From 1fd02e46100b9ce51c8124d9d702553b9f1d2a21 Mon Sep 17 00:00:00 2001 From: marwa-wesleti <marwa.wesleti007@gmail/com> Date: Thu, 3 Apr 2025 01:45:51 +0100 Subject: [PATCH] final version --- pom.xml | 13 + .../jobmngt/config/AppUserDetailsService.java | 44 +++ .../imt/inf211/jobmngt/config/CheckAuth.java | 16 + .../jobmngt/config/DataInitializer.java | 76 ++++ .../inf211/jobmngt/config/SecurityConfig.java | 50 +++ .../controller/ApplicationController.java | 267 ++++++++------ .../controller/CandidateController.java | 69 +++- .../jobmngt/controller/CompanyController.java | 191 +++++----- .../controller/JobOfferController.java | 244 +++++++------ .../jobmngt/controller/LoginController.java | 125 ++++--- .../jobmngt/converter/CompanyConverter.java | 2 +- .../imt/inf211/jobmngt/dao/AppUserDao.java | 4 +- .../inf211/jobmngt/dao/ApplicationDao.java | 30 ++ .../imt/inf211/jobmngt/dao/CandidateDao.java | 10 +- .../imt/inf211/jobmngt/dao/CompanyDao.java | 4 +- .../imt/inf211/jobmngt/dao/JobOfferDao.java | 10 +- .../jobmngt/dao/QualificationLevelDao.java | 16 +- .../imt/inf211/jobmngt/dao/SectorDao.java | 26 +- .../imt/inf211/jobmngt/entity/AppUser.java | 44 ++- .../inf211/jobmngt/entity/Application.java | 18 +- .../imt/inf211/jobmngt/entity/Candidate.java | 25 +- .../imt/inf211/jobmngt/entity/Company.java | 4 +- .../imt/inf211/jobmngt/entity/JobOffer.java | 29 +- .../entity/{Role.java => RoleType.java} | 6 +- .../jobmngt/service/AppUserService.java | 2 +- .../jobmngt/service/AppUserServiceImpl.java | 2 +- .../jobmngt/service/CandidateService.java | 5 + .../jobmngt/service/CandidateServiceImpl.java | 24 +- .../jobmngt/service/CompanyService.java | 8 +- .../jobmngt/service/CompanyServiceImpl.java | 23 +- .../jobmngt/service/JobOfferService.java | 6 +- .../jobmngt/service/JobOfferServiceImpl.java | 9 +- .../jobmngt/service/MatchingIndexService.java | 134 +++++++ .../inf211/jobmngt/service/SectorService.java | 11 +- .../jobmngt/service/SectorServiceImpl.java | 15 +- src/main/resources/application.properties | 11 +- src/main/resources/static/css/gyj_imt.css | 92 ++++- .../application/application-confirmation.html | 158 ++++++-- .../application/application-details.html | 140 +++++-- .../application/application-edit.html | 257 ++++++++++++- .../application/application-list.html | 180 +++++---- .../application/application-update-form.html | 11 +- .../templates/application/apply.html | 254 +++++++++---- .../templates/baseTemplate/base.html | 74 ++-- .../templates/baseTemplate/footer.html | 2 +- .../templates/baseTemplate/head.html | 2 + .../resources/templates/baseTemplate/nav.html | 41 +-- .../templates/candidate/candidates-list.html | 138 +++++-- .../templates/candidate/confirmation.html | 119 ++++-- .../templates/candidate/confirmationSupp.html | 2 +- .../templates/candidate/details.html | 135 +++++-- .../templates/candidate/editCandidate.html | 192 ++++++++-- .../templates/candidate/signupCandidate.html | 25 +- .../templates/company/companyBase.html | 13 - .../templates/company/companyEdit.html | 189 +++++++--- .../templates/company/companyForm.html | 25 +- .../templates/company/companyList.html | 147 +++++--- .../templates/company/companyView.html | 218 +++++------ .../templates/error/accessDenied.html | 2 +- src/main/resources/templates/index.html | 29 +- .../jobOffer/companyJobOfferView.html | 15 +- .../templates/jobOffer/jobOfferEdit.html | 217 +++++++++++ .../templates/jobOffer/jobOfferForm.html | 208 ++++++++--- .../templates/jobOffer/jobOfferList.html | 195 ++++++---- .../templates/jobOffer/jobOfferView.html | 125 ++++--- src/main/resources/templates/login.html | 194 ++++++++-- .../qualificationLevelList.html | 338 +++++++++++++++-- .../templates/sector/sectorList.html | 343 +++++++++++++++--- target/classes/application.properties | 11 +- .../config/AppUserDetailsService.class | Bin 0 -> 3776 bytes .../imt/inf211/jobmngt/config/CheckAuth.class | Bin 0 -> 1052 bytes .../jobmngt/config/DataInitializer.class | Bin 0 -> 3887 bytes .../jobmngt/config/SecurityConfig.class | Bin 0 -> 3634 bytes .../controller/ApplicationController.class | Bin 11829 -> 14037 bytes .../controller/CandidateController.class | Bin 5731 -> 7288 bytes .../controller/CompanyController.class | Bin 7997 -> 9948 bytes .../controller/JobOfferController.class | Bin 11006 -> 11695 bytes .../jobmngt/controller/LoginController.class | Bin 4092 -> 5267 bytes .../jobmngt/controller/SectorController.class | Bin 1348 -> 1354 bytes .../jobmngt/converter/CompanyConverter.class | Bin 1641 -> 1583 bytes .../imt/inf211/jobmngt/dao/AppUserDao.class | Bin 6141 -> 6168 bytes .../inf211/jobmngt/dao/ApplicationDao.class | Bin 3313 -> 4860 bytes .../imt/inf211/jobmngt/dao/CandidateDao.class | Bin 3129 -> 3199 bytes .../imt/inf211/jobmngt/dao/CompanyDao.class | Bin 4249 -> 4215 bytes .../imt/inf211/jobmngt/dao/JobOfferDao.class | Bin 1467 -> 1443 bytes .../jobmngt/dao/QualificationLevelDao.class | Bin 5204 -> 4889 bytes .../imt/inf211/jobmngt/dao/SectorDao.class | Bin 5893 -> 6287 bytes .../imt/inf211/jobmngt/entity/AppUser.class | Bin 3091 -> 4184 bytes .../inf211/jobmngt/entity/Application.class | Bin 4322 -> 4396 bytes .../imt/inf211/jobmngt/entity/Candidate.class | Bin 1573 -> 2962 bytes .../imt/inf211/jobmngt/entity/Company.class | Bin 2656 -> 2825 bytes .../imt/inf211/jobmngt/entity/JobOffer.class | Bin 5941 -> 6045 bytes .../imt/inf211/jobmngt/entity/Role.class | Bin 1215 -> 0 bytes .../imt/inf211/jobmngt/entity/RoleType.class | Bin 0 -> 1243 bytes .../jobmngt/service/AppUserService.class | Bin 696 -> 681 bytes .../jobmngt/service/AppUserServiceImpl.class | Bin 1839 -> 1818 bytes .../jobmngt/service/CandidateService.class | Bin 1027 -> 1195 bytes .../service/CandidateServiceImpl.class | Bin 3143 -> 4206 bytes .../jobmngt/service/CompanyService.class | Bin 1114 -> 1024 bytes .../jobmngt/service/CompanyServiceImpl.class | Bin 3284 -> 3159 bytes .../jobmngt/service/JobOfferService.class | Bin 1083 -> 1008 bytes .../jobmngt/service/JobOfferServiceImpl.class | Bin 3902 -> 3810 bytes .../service/MatchingIndexService.class | Bin 0 -> 10294 bytes .../jobmngt/service/SectorService.class | Bin 1010 -> 923 bytes .../jobmngt/service/SectorServiceImpl.class | Bin 2744 -> 2486 bytes target/classes/static/css/gyj_imt.css | 92 ++++- .../application/application-confirmation.html | 158 ++++++-- .../application/application-details.html | 140 +++++-- .../application/application-edit.html | 257 ++++++++++++- .../application/application-list.html | 180 +++++---- .../application/application-update-form.html | 11 +- .../classes/templates/application/apply.html | 254 +++++++++---- .../classes/templates/baseTemplate/base.html | 74 ++-- .../templates/baseTemplate/footer.html | 2 +- .../classes/templates/baseTemplate/head.html | 2 + .../classes/templates/baseTemplate/nav.html | 41 +-- .../templates/candidate/candidates-list.html | 138 +++++-- .../templates/candidate/confirmation.html | 119 ++++-- .../templates/candidate/confirmationSupp.html | 2 +- .../classes/templates/candidate/details.html | 135 +++++-- .../templates/candidate/editCandidate.html | 192 ++++++++-- .../templates/candidate/signupCandidate.html | 25 +- .../templates/company/companyBase.html | 13 - .../templates/company/companyEdit.html | 189 +++++++--- .../templates/company/companyForm.html | 25 +- .../templates/company/companyList.html | 147 +++++--- .../templates/company/companyView.html | 218 +++++------ .../classes/templates/error/accessDenied.html | 2 +- target/classes/templates/index.html | 29 +- .../jobOffer/companyJobOfferView.html | 15 +- .../templates/jobOffer/jobOfferEdit.html | 217 +++++++++++ .../templates/jobOffer/jobOfferForm.html | 208 ++++++++--- .../templates/jobOffer/jobOfferList.html | 195 ++++++---- .../templates/jobOffer/jobOfferView.html | 125 ++++--- target/classes/templates/login.html | 194 ++++++++-- .../qualificationLevelList.html | 338 +++++++++++++++-- .../classes/templates/sector/sectorList.html | 343 +++++++++++++++--- .../compile/default-compile/createdFiles.lst | 3 + .../compile/default-compile/inputFiles.lst | 91 ++--- 139 files changed, 7463 insertions(+), 2375 deletions(-) create mode 100644 src/main/java/fr/atlantique/imt/inf211/jobmngt/config/AppUserDetailsService.java create mode 100644 src/main/java/fr/atlantique/imt/inf211/jobmngt/config/CheckAuth.java create mode 100644 src/main/java/fr/atlantique/imt/inf211/jobmngt/config/DataInitializer.java create mode 100644 src/main/java/fr/atlantique/imt/inf211/jobmngt/config/SecurityConfig.java rename src/main/java/fr/atlantique/imt/inf211/jobmngt/entity/{Role.java => RoleType.java} (50%) create mode 100644 src/main/java/fr/atlantique/imt/inf211/jobmngt/service/MatchingIndexService.java delete mode 100644 src/main/resources/templates/company/companyBase.html create mode 100644 src/main/resources/templates/jobOffer/jobOfferEdit.html create mode 100644 target/classes/fr/atlantique/imt/inf211/jobmngt/config/AppUserDetailsService.class create mode 100644 target/classes/fr/atlantique/imt/inf211/jobmngt/config/CheckAuth.class create mode 100644 target/classes/fr/atlantique/imt/inf211/jobmngt/config/DataInitializer.class create mode 100644 target/classes/fr/atlantique/imt/inf211/jobmngt/config/SecurityConfig.class delete mode 100644 target/classes/fr/atlantique/imt/inf211/jobmngt/entity/Role.class create mode 100644 target/classes/fr/atlantique/imt/inf211/jobmngt/entity/RoleType.class create mode 100644 target/classes/fr/atlantique/imt/inf211/jobmngt/service/MatchingIndexService.class delete mode 100644 target/classes/templates/company/companyBase.html create mode 100644 target/classes/templates/jobOffer/jobOfferEdit.html diff --git a/pom.xml b/pom.xml index 8355737..d235935 100644 --- a/pom.xml +++ b/pom.xml @@ -43,6 +43,7 @@ <artifactId>spring-boot-starter-web</artifactId> </dependency> + <dependency> <groupId>org.springframework.boot</groupId> @@ -58,6 +59,18 @@ <artifactId>postgresql</artifactId> <version>42.6.0</version> </dependency> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + <optional>true</optional> + </dependency> + + <!-- Spring Boot Starter Security --> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-security</artifactId> + </dependency> + diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/config/AppUserDetailsService.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/config/AppUserDetailsService.java new file mode 100644 index 0000000..f5fb1ab --- /dev/null +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/config/AppUserDetailsService.java @@ -0,0 +1,44 @@ +package fr.atlantique.imt.inf211.jobmngt.config; + +import fr.atlantique.imt.inf211.jobmngt.dao.AppUserDao; +import fr.atlantique.imt.inf211.jobmngt.entity.AppUser; +import fr.atlantique.imt.inf211.jobmngt.entity.Company; +import fr.atlantique.imt.inf211.jobmngt.entity.RoleType; +import fr.atlantique.imt.inf211.jobmngt.service.CompanyService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +@Service +public class AppUserDetailsService implements UserDetailsService { + + @Autowired + private AppUserDao appUserDao; + private static final Logger logger = LoggerFactory.getLogger(AppUserDetailsService.class); + + + @Override + public UserDetails loadUserByUsername(String mail) throws UsernameNotFoundException { + logger.info("Tentative de connexion avec l'email : {}", mail); + + AppUser user = appUserDao.findByMail(mail) + .orElseThrow(() -> { + logger.error("Utilisateur non trouvé : {}", mail); + return new UsernameNotFoundException("User not found"); + }); + + logger.info("Utilisateur trouvé : {}", user.getMail()); + logger.info("Rôle de l'utilisateur : {}", user.getUsertype()); + + return User.builder() + .username(user.getMail()) + .password(user.getPassword()) + .roles("ROLE_" + user.getUsertype()) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/config/CheckAuth.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/config/CheckAuth.java new file mode 100644 index 0000000..4bdf6f4 --- /dev/null +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/config/CheckAuth.java @@ -0,0 +1,16 @@ +package fr.atlantique.imt.inf211.jobmngt.config; + +import fr.atlantique.imt.inf211.jobmngt.entity.AppUser; +import jakarta.servlet.http.HttpSession; + +public class CheckAuth { + public static boolean isUserAuthenticated(HttpSession session) { + return session != null && session.getAttribute("loggedInUser") != null; + } + + public static boolean isUserAuthenticated(HttpSession session, int id) { + AppUser user =session != null && session.getAttribute("loggedInUser") != null? + (AppUser) session.getAttribute("loggedInUser"):null; + return user != null && user.getId() == id; + } +} diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/config/DataInitializer.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/config/DataInitializer.java new file mode 100644 index 0000000..b490a6d --- /dev/null +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/config/DataInitializer.java @@ -0,0 +1,76 @@ +package fr.atlantique.imt.inf211.jobmngt.config; + +import fr.atlantique.imt.inf211.jobmngt.dao.QualificationLevelDao; +import fr.atlantique.imt.inf211.jobmngt.dao.SectorDao; +import fr.atlantique.imt.inf211.jobmngt.entity.QualificationLevel; +import fr.atlantique.imt.inf211.jobmngt.entity.Sector; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@RequiredArgsConstructor +public class DataInitializer { + + final SectorDao sectorDao; + final QualificationLevelDao qualificationLevelDao; + + + @PostConstruct + public void init() { + initSectors(); + initQualificationLevels(); + } + + public void initSectors() { + List<String> sectorNames = List.of( + "Purchase/Logistic", + "Administration", + "Agriculture", + "Agrofood", + "Insurance", + "Audit/Advise/Expertise", + "Public works/Real estate", + "Trade", + "Communication/Art/Media/Fashion", + "Industry/Engineering/Production", + "Computer science", + "Juridique/Fiscal/Droit", + "Marketing", + "Public/Parapublic", + "Human resources", + "Information Technology", + "Software Engineering", + "Telecommunications", + "Health/Social/Biology/Humanitarian", + "Telecom/Networking" + ); + + sectorNames.forEach(name -> { + if (!sectorDao.existsByLabel(name)) { + Sector sector = new Sector(); + sector.setLabel(name); + sectorDao.persist(sector); + } + }); + } + public void initQualificationLevels() { + List<String> levels = List.of( + "Professional level", + "A-diploma", + "Licence", + "Master", + "PhD" + ); + + levels.forEach(level -> { + if (!qualificationLevelDao.existsByLabel(level)) { + QualificationLevel ql = new QualificationLevel(); + ql.setLabel(level); + qualificationLevelDao.persist(ql); + } + }); + } +} \ No newline at end of file diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/config/SecurityConfig.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/config/SecurityConfig.java new file mode 100644 index 0000000..d95e57e --- /dev/null +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/config/SecurityConfig.java @@ -0,0 +1,50 @@ +package fr.atlantique.imt.inf211.jobmngt.config; + +import fr.atlantique.imt.inf211.jobmngt.entity.RoleType; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .authorizeHttpRequests(auth -> auth + .requestMatchers( + "/", + "/login", + "/logout", + "/companies/**", + "/candidates/**", + "/jobs/**", + "/jobs/update", + "/applications/**", + "/qualificationLevels/**", + "/sectors/**", + "/auth/**", + "/index.html", + "/static/**", + "/css/**", + "/js/**", + "/img/**" + ).permitAll() + .anyRequest().authenticated() + ); + + return http.build(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + +} \ No newline at end of file diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/controller/ApplicationController.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/controller/ApplicationController.java index 6a1d93b..d9b0233 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/controller/ApplicationController.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/controller/ApplicationController.java @@ -1,72 +1,65 @@ package fr.atlantique.imt.inf211.jobmngt.controller; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.logging.Logger; - +import java.util.stream.Collectors; + +import fr.atlantique.imt.inf211.jobmngt.config.CheckAuth; +import fr.atlantique.imt.inf211.jobmngt.entity.*; +import fr.atlantique.imt.inf211.jobmngt.service.*; +import jakarta.persistence.EntityNotFoundException; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.hibernate.Hibernate; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import org.springframework.web.servlet.mvc.support.RedirectAttributes; -import fr.atlantique.imt.inf211.jobmngt.entity.Application; -import fr.atlantique.imt.inf211.jobmngt.entity.Candidate; -import fr.atlantique.imt.inf211.jobmngt.entity.QualificationLevel; -import fr.atlantique.imt.inf211.jobmngt.entity.Sector; -import fr.atlantique.imt.inf211.jobmngt.service.ApplicationService; -import fr.atlantique.imt.inf211.jobmngt.service.CandidateService; -import fr.atlantique.imt.inf211.jobmngt.service.QualificationLevelService; -import fr.atlantique.imt.inf211.jobmngt.service.SectorService; import jakarta.servlet.http.HttpSession; @Controller @RequestMapping("/applications") +@RequiredArgsConstructor public class ApplicationController { private static final Logger logger = Logger.getLogger(ApplicationController.class.getName()); - @Autowired - private ApplicationService applicationService; + final ApplicationService applicationService; + + final CandidateService candidateService; - @Autowired - private CandidateService candidateService; + final QualificationLevelService qualificationLevelService; + + final SectorService sectorService; + final MatchingIndexService matchingIndexService; - @Autowired - private QualificationLevelService qualificationLevelService; - @Autowired - private SectorService sectorService; /** * Affiche le formulaire de candidature. */ @GetMapping("/apply") - public String showApplicationForm(HttpSession session, Model model) { - Integer userId = (Integer) session.getAttribute("uid"); - - if (userId == null) { - model.addAttribute("error", "Vous devez être connecté pour postuler."); + public String showApplicationForm(Model model, HttpServletRequest request) { + if (!CheckAuth.isUserAuthenticated(request.getSession())) { return "redirect:/login"; } - Optional<Candidate> candidateOpt = candidateService.findById(userId); - if (candidateOpt.isEmpty()) { - model.addAttribute("error", "Utilisateur non trouvé."); - return "error"; - } - - Candidate candidate = candidateOpt.get(); List<QualificationLevel> qualifications = qualificationLevelService.getAllQualificationLevels(); - List<Sector> sectors = sectorService.getAllSectors(); + Collection<Sector> sectors = sectorService.getAllSectors(); model.addAttribute("application", new Application()); - model.addAttribute("candidate", candidate); model.addAttribute("qualifications", qualifications); model.addAttribute("sectors", sectors); @@ -79,21 +72,22 @@ public class ApplicationController { @PostMapping("/apply") public String submitApplication( @RequestParam("cv") String cv, + @RequestParam("publicationDate") LocalDate date, @RequestParam("qualificationLevel") int qualificationLevelId, @RequestParam("sectors") List<Integer> sectorIds, - HttpSession session, + HttpServletRequest request, RedirectAttributes redirectAttributes) { - Integer userId = (Integer) session.getAttribute("uid"); - if (userId == null) { - return "redirect:/login?error=Vous devez être connecté pour postuler."; + if (!CheckAuth.isUserAuthenticated(request.getSession())) { + return "redirect:/login"; } - Optional<Candidate> candidateOpt = candidateService.findById(userId); + Optional<Candidate> candidateOpt = candidateService.findById((Integer) request.getSession().getAttribute("userId")); if (candidateOpt.isEmpty()) { redirectAttributes.addFlashAttribute("error", "Utilisateur non trouvé."); return "redirect:/error"; } + Application application = new Application(); Candidate candidate = candidateOpt.get(); Optional<QualificationLevel> qualificationLevelOpt = qualificationLevelService.findById(qualificationLevelId); @@ -104,24 +98,24 @@ public class ApplicationController { } QualificationLevel qualificationLevel = qualificationLevelOpt.get(); - List<Sector> sectors = sectorService.getSectorsByIds(sectorIds); + Collection<Sector> sectors = sectorService.getSectorsByIds(sectorIds); if (sectors.isEmpty()) { redirectAttributes.addFlashAttribute("error", "Vous devez sélectionner au moins un secteur."); return "redirect:/error"; } - Application application = new Application(); + application.setCandidate(candidate); application.setCv(cv); application.setQualificationlevel(qualificationLevel); application.setSectors(sectors); - application.setAppdate(LocalDateTime.now()); + application.setAppdate(date); Application savedApplication = applicationService.save(application); // Stocke uniquement l'ID en session - session.setAttribute("lastApplicationId", savedApplication.getId()); + request.getSession().setAttribute("lastApplicationId", savedApplication.getId()); return "redirect:/applications/confirmation"; } @@ -130,30 +124,25 @@ public class ApplicationController { * Affiche la page de confirmation avec les détails de la candidature. */ @GetMapping("/confirmation") - public String showConfirmationPage(Model model, HttpSession session) { - Integer lastApplicationId = (Integer) session.getAttribute("lastApplicationId"); - - if (lastApplicationId == null) { - System.out.println(" Aucun ID de candidature enregistré."); - return "redirect:/error"; - } - - Optional<Application> applicationOpt = applicationService.findById(lastApplicationId); - if (applicationOpt.isEmpty()) { - System.out.println(" Aucune candidature trouvée en base de données !"); - return "redirect:/error"; + public String showConfirmationPage(Model model, HttpServletRequest request) { + if (!CheckAuth.isUserAuthenticated(request.getSession())) { + return "redirect:/login"; } + Integer lastApplicationId = (Integer) request.getSession().getAttribute("lastApplicationId"); + Application application = applicationService.findById(lastApplicationId).orElseThrow(() -> new EntityNotFoundException("Application not found"));; // Méthode custom - Application application = applicationOpt.get(); + // Force l'initialisation avant de fermer la session + Hibernate.initialize(application.getQualificationlevel()); + Hibernate.initialize(application.getSectors()); - // Vérifier si l'objet a bien été récupéré - System.out.println(" Candidature trouvée : " + application.getId()); - System.out.println(" CV : " + application.getCv()); - System.out.println(" Qualification : " + (application.getQualificationlevel() != null ? application.getQualificationlevel().getLabel() : "NULL")); - System.out.println(" Secteurs : " + (application.getSectors() != null ? application.getSectors().size() : "NULL")); - System.out.println(" Date : " + application.getAppdate()); + // Ajoutez explicitement tous les attributs nécessaires + model.addAttribute("appId", application.getId()); + model.addAttribute("appCv", application.getCv()); + model.addAttribute("appDate", application.getAppdate()); + model.addAttribute("qualification", application.getQualificationlevel()); + model.addAttribute("sectors", application.getSectors()); + model.addAttribute("appD", application.getAppdate()); - model.addAttribute("application", application); return "application/application-confirmation"; } @@ -162,7 +151,9 @@ public class ApplicationController { List<Application> applications = applicationService.getAllApplications(); if (applications.isEmpty()) { - model.addAttribute("error", "Aucune candidature trouvée."); + model.addAttribute("error", "No applications found"); + return "redirect:/list"; + } model.addAttribute("applicationsList", applications); // Renommage de la variable @@ -173,14 +164,17 @@ public class ApplicationController { */ @GetMapping("/details/{id}") - public String showApplicationDetails(@PathVariable int id, Model model) { - Optional<Application> applicationOpt = applicationService.findById(id); - if (applicationOpt.isEmpty()) { - model.addAttribute("error", "Candidature non trouvée."); - return "error"; // Page d'erreur si l'application n'est pas trouvée - } - Application application = applicationOpt.get(); - model.addAttribute("application", application); + public String showApplicationDetails(@PathVariable int id, Model model, HttpServletRequest request) { + + Application application = applicationService.findById(id).orElseThrow(); + + // Test avec des valeurs directes + model.addAttribute("cv", application.getCv()); + model.addAttribute("sectors", application.getSectors()); + model.addAttribute("appdate", application.getAppdate()); + model.addAttribute("qualification", application.getQualificationlevel()); + model.addAttribute("candidate", application.getCandidate()); + return "application/application-details"; } @@ -189,48 +183,55 @@ public class ApplicationController { @GetMapping("/edit/{id}") public String showEditForm(@PathVariable int id, Model model, HttpSession session) { - Integer userId = (Integer) session.getAttribute("uid"); - - if (userId == null) { - return "redirect:/login"; // Rediriger vers login si non connecté + if (!CheckAuth.isUserAuthenticated(session)) { + return "redirect:/login"; } - Optional<Application> applicationOpt = applicationService.findById(id); - if (applicationOpt.isEmpty()) { - model.addAttribute("error", "Candidature non trouvée."); - return "error"; - } + // Récupération avec vérification robuste + Application application = applicationService.findById(id) + .orElseThrow(() -> new IllegalArgumentException("Invalid application id: " + id)); - Application application = applicationOpt.get(); + // LOG important + logger.info("Editing application ID: " + application.getId()); - // Vérification si l'utilisateur est bien le propriétaire - if (application.getCandidate().getId() != userId) { - model.addAttribute("error", "Vous ne pouvez modifier que vos propres candidatures."); - return "error"; - } + // Initialisation des relations + Hibernate.initialize(application.getQualificationlevel()); + Hibernate.initialize(application.getSectors()); - List<QualificationLevel> qualifications = qualificationLevelService.getAllQualificationLevels(); - List<Sector> sectors = sectorService.getAllSectors(); + // Vérification null + if(application.getSectors() == null) { + application.setSectors(new HashSet<>()); + } - model.addAttribute("application", application); - model.addAttribute("qualifications", qualifications); - model.addAttribute("sectors", sectors); + model.addAttribute("id", application.getId()); + model.addAttribute("cv", application.getCv()); + model.addAttribute("candidate", application.getCandidate()); + model.addAttribute("sectorApp", application.getSectors()); + model.addAttribute("qualApp", application.getQualificationlevel()); + model.addAttribute("appDate", application.getAppdate()); + model.addAttribute("qualifications", qualificationLevelService.getAllQualificationLevels()); + model.addAttribute("sectors", sectorService.listOfSectors()); - return "application/application-edit"; + return "application/application-edit"; // Chemin exact } - /** - * Traite la modification d'une candidature - */ @PostMapping("/update") - public String updateApplication( + public String updateJobOffer( @RequestParam("id") int id, - @RequestParam("cv") String cv, - @RequestParam("qualificationLevel") int qualificationLevelId, - @RequestParam("sectors") List<Integer> sectorIds, - RedirectAttributes redirectAttributes) { + @RequestParam(value = "cv", required = false) String newCv, + @RequestParam(value = "publicationDate", required = false) LocalDate newAppDate, + @RequestParam(value = "qualificationLevel", required = false) Integer qualificationLevelId, + @RequestParam(value = "sectorIds", required = false) List<Integer> sectorIds, + RedirectAttributes redirectAttributes, + HttpServletRequest request) { + + // Vérification d'authentification + if (!CheckAuth.isUserAuthenticated(request.getSession())) { + return "redirect:/login"; + } + // Récupération de l'offre existante Optional<Application> applicationOpt = applicationService.findById(id); if (applicationOpt.isEmpty()) { redirectAttributes.addFlashAttribute("error", "Candidature non trouvée."); @@ -238,20 +239,51 @@ public class ApplicationController { } Application application = applicationOpt.get(); - application.setCv(cv); - Optional<QualificationLevel> qualificationLevelOpt = qualificationLevelService.findById(qualificationLevelId); - if (qualificationLevelOpt.isPresent()) { - application.setQualificationlevel(qualificationLevelOpt.get()); + // Mise à jour conditionnelle des champs + if (newCv != null && !newCv.isEmpty()) { + application.setCv(newCv); } + // Si newCv est null ou vide, on conserve l'ancienne valeur - List<Sector> sectors = sectorService.getSectorsByIds(sectorIds); - application.setSectors(sectors); - - applicationService.save(application); - redirectAttributes.addFlashAttribute("success", "Candidature mise à jour avec succès."); + if (newAppDate != null) { + application.setAppdate(newAppDate); + } + // Si newAppDate est null, on conserve l'ancienne date - return "redirect:/applications/list"; + // Mise à jour conditionnelle du niveau de qualification + if (qualificationLevelId != null) { + Optional<QualificationLevel> qualificationLevel = qualificationLevelService.findById(qualificationLevelId); + qualificationLevel.ifPresent(application::setQualificationlevel); + } + // Si qualificationLevelId est null, on conserve l'ancienne qualification + + // Mise à jour conditionnelle des secteurs + if (sectorIds != null) { + if (!sectorIds.isEmpty()) { + Collection<Sector> selectedSectors = sectorService.getSectorsByIds(sectorIds); + application.setSectors(selectedSectors); + } else { + application.setSectors(new HashSet<>()); + } + } + // Si sectorIds est null, on conserve les anciens secteurs + + try { + // Sauvegarde de l'offre mise à jour + applicationService.save(application); + + // Mise à jour de l'index de matching si nécessaire + matchingIndexService.updateIndex(application, true); + + // Stocke uniquement l'ID en session + request.getSession().setAttribute("lastApplicationId", application.getId()); + redirectAttributes.addFlashAttribute("successMessage", "Candidature mise à jour avec succès !"); + return "redirect:/applications/confirmation"; + } catch (Exception e) { + redirectAttributes.addFlashAttribute("errorMessage", "Erreur lors de la mise à jour: " + e.getMessage()); + return "redirect:/applications/edit/" + id; + } } /** @@ -259,7 +291,10 @@ public class ApplicationController { */ @GetMapping("/delete/{id}") public String deleteApplication(@PathVariable int id, RedirectAttributes redirectAttributes, HttpSession session) { - Integer userId = (Integer) session.getAttribute("uid"); + if (!CheckAuth.isUserAuthenticated(session)) { + return "redirect:/login"; + } + Integer userId = (Integer) session.getAttribute("userId"); if (userId == null) { return "redirect:/login"; diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/controller/CandidateController.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/controller/CandidateController.java index 24996e0..0e14059 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/controller/CandidateController.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/controller/CandidateController.java @@ -2,6 +2,10 @@ package fr.atlantique.imt.inf211.jobmngt.controller; import java.util.List; +import fr.atlantique.imt.inf211.jobmngt.config.CheckAuth; +import fr.atlantique.imt.inf211.jobmngt.entity.AppUser; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpSession; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -14,6 +18,7 @@ import org.springframework.web.servlet.ModelAndView; import fr.atlantique.imt.inf211.jobmngt.entity.Candidate; import fr.atlantique.imt.inf211.jobmngt.service.CandidateService; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; @Controller @RequestMapping("/candidates") @@ -29,29 +34,32 @@ public class CandidateController { } @PostMapping("/signup") - public ModelAndView registerCandidate(@RequestParam String mail, + public String registerCandidate(@RequestParam String mail, @RequestParam String password, @RequestParam String lastname, @RequestParam String firstname, - @RequestParam String city) { + @RequestParam String city, RedirectAttributes redirectAttributes + ) { + boolean isRegistered = candidateService.registerCandidate(new Candidate(mail, password, city, lastname, firstname)); if (!isRegistered) { - ModelAndView mav = new ModelAndView("candidate/signupCandidate"); - mav.addObject("error", "This email is already registered. Please use another one."); - return mav; + redirectAttributes.addFlashAttribute("error", "This email is already registered. Please use another one."); + return "candidate/signupCandidate"; } - // Redirection vers confirmation avec un message spécifique - ModelAndView mav = new ModelAndView("candidate/confirmation"); - mav.addObject("message", "Your account has been successfully created!"); - return mav; + redirectAttributes.addFlashAttribute("success", "Entreprise créée avec succès !"); + return "redirect:/candidates/list"; // Redirige vers la liste des entreprises } @GetMapping("/list") public String listCandidates(Model model) { List<Candidate> candidates = candidateService.getAllCandidates(); + if (candidates.isEmpty()) { + model.addAttribute("error", "Aucune candidat trouvée."); + } + model.addAttribute("candidates", candidates); return "candidate/candidates-list"; } @@ -69,11 +77,17 @@ public class CandidateController { return "error"; } model.addAttribute("candidate", candidate); + System.out.println(candidateService.getApplicationByCandidate(id)); + // Change this line: + model.addAttribute("candidateApplications", candidate.getCandidatApplications()); return "candidate/details"; } @GetMapping("/edit/{id}") - public String showEditForm(@PathVariable int id, Model model) { + public String showEditForm(@PathVariable int id, Model model,HttpServletRequest request) { + if (!CheckAuth.isUserAuthenticated(request.getSession(),id)) { + return "redirect:/login"; + } Candidate candidate = candidateService.getCandidateById(id); if (candidate == null) { model.addAttribute("error", "Le candidat n'existe pas."); @@ -89,7 +103,10 @@ public class CandidateController { @RequestParam String lastname, @RequestParam String firstname, @RequestParam String city, - Model model) { + Model model,HttpServletRequest request) { + if (!CheckAuth.isUserAuthenticated(request.getSession(),id)) { + return "redirect:/login"; + } Candidate candidate = candidateService.getCandidateById(id); if (candidate == null) { model.addAttribute("error", "Le candidat n'existe pas."); @@ -110,19 +127,35 @@ public class CandidateController { } @GetMapping("/delete/{id}") - public String deleteCandidate(@PathVariable int id, Model model) { - Candidate candidate = candidateService.getCandidateById(id); + public String deleteCandidate( + @PathVariable int id, + HttpServletRequest request, + RedirectAttributes redirectAttributes) { + if (!CheckAuth.isUserAuthenticated(request.getSession(),id)) { + return "redirect:/login"; + } + Candidate candidate = candidateService.getCandidateById(id); if (candidate == null) { - model.addAttribute("error", "Le candidat avec l'ID " + id + " n'existe pas."); - return "error"; // Affiche une page d'erreur si le candidat n'existe pas + redirectAttributes.addFlashAttribute("error", + "Candidate with ID " + id + " not found"); + return "redirect:/candidates/list"; } candidateService.deleteCandidate(id); - // Rediriger vers la liste avec un message de confirmation - model.addAttribute("message", "Le candidat " + candidate.getFirstname() + " " + candidate.getLastname() + " a été supprimé avec succès !"); - return "candidate/confirmationSupp"; // Affichage de la confirmation après suppression + // Invalidation de session si nécessaire + HttpSession session = request.getSession(false); + if (session != null) { + AppUser loggedInUser = (AppUser) session.getAttribute("loggedInUser"); + if (loggedInUser != null && loggedInUser.getId() == id) { + session.invalidate(); + return "redirect:/login?logout"; + } + } + + redirectAttributes.addFlashAttribute("deletedCandidate", candidate); + return "redirect:/candidates/delete/confirmation"; } } diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/controller/CompanyController.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/controller/CompanyController.java index cdd3904..5a5913c 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/controller/CompanyController.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/controller/CompanyController.java @@ -4,92 +4,87 @@ import java.util.HashSet; import java.util.List; import java.util.Optional; +import fr.atlantique.imt.inf211.jobmngt.config.CheckAuth; +import fr.atlantique.imt.inf211.jobmngt.entity.*; import fr.atlantique.imt.inf211.jobmngt.service.JobOfferService; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpSession; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.mvc.support.RedirectAttributes; -import fr.atlantique.imt.inf211.jobmngt.entity.Company; -import fr.atlantique.imt.inf211.jobmngt.entity.JobOffer; import fr.atlantique.imt.inf211.jobmngt.service.CompanyService; @Controller +@RequiredArgsConstructor public class CompanyController { - @Autowired - private CompanyService companyService; - private final JobOfferService jobOfferService; + final CompanyService companyService; + final JobOfferService jobOfferService; + final PasswordEncoder passwordEncoder; - public CompanyController(CompanyService companyService, JobOfferService jobOfferService) { - this.jobOfferService = jobOfferService; - this.companyService = companyService; - } - @GetMapping("/jobOffers/view/{id}") - public String viewJobOffer(@PathVariable("id") Long id, Model model) { - Optional<JobOffer> offerOpt = jobOfferService.findById(id); - if (offerOpt.isPresent()) { - model.addAttribute("offer", offerOpt.get()); - return "jobOffer/jobOfferView"; - } - return "redirect:/companies"; // Redirige si l'offre n'est pas trouvée + // Affiche le formulaire d'inscription + @GetMapping("/companies/create") + public String showCreateForm(Model model) { + // On passe un objet 'Company' vide pour lier les champs du formulaire + model.addAttribute("company", new Company()); + return "company/companyForm"; } + // Enregistre une entreprise avec vérification des doublons et message de succès + @PostMapping("/companies/create") + public String registerCompany(@ModelAttribute("company") Company company, + BindingResult bindingResult, + RedirectAttributes redirectAttributes) { - - - - // // // Afficher les offres d'emploi d'une entreprise spécifique - @GetMapping("/companies/{id}/jobOffers") - public String listCompanyJobOffers(@PathVariable("id") Long companyId, Model model) { - Optional<Company> companyOpt = companyService.findById(companyId); - if (companyOpt.isPresent()) { - Company company = companyOpt.get(); - List<JobOffer> jobOffers = jobOfferService.findByCompany(company); - model.addAttribute("company", company); - model.addAttribute("jobOffers", jobOffers); - return "company/jobOffers"; + if (bindingResult.hasErrors()) { + return "company/companyForm"; } - model.addAttribute("errorMessage", "Entreprise introuvable !"); - return "error"; - - - } - - - - + Optional<Company> existingCompany = companyService.findByMail(company.getMail()); + if (existingCompany.isPresent()) { + redirectAttributes.addFlashAttribute("error", "This user already exist "); + return "redirect:/companies/create"; + } + try { + company.setPassword(passwordEncoder.encode(company.getPassword())); + company.setUsertype(RoleType.Company.name()); + companyService.saveCompany(company); + redirectAttributes.addFlashAttribute("success", "Entreprise créée avec succès !"); + return "redirect:/companies"; // Redirige vers la liste des entreprises - // Affiche le formulaire d'inscription - @GetMapping("/companies/create") - public String showCreateForm(Model model) { - // On passe un objet 'Company' vide pour lier les champs du formulaire - model.addAttribute("company", new Company()); - return "company/companyForm"; + } catch (Exception e) { + bindingResult.reject("error.global", "Erreur technique : " + e.getMessage()); + return "company/companyForm"; + } } - // Affiche la liste des entreprises @GetMapping("/companies") public String listCompanies(Model model) { - model.addAttribute("companies", companyService.getAllCompanies()); + List<Company> companies = companyService.getAllCompanies(); + if (companies.isEmpty()) { + model.addAttribute("error", "Aucune entreprise trouvée."); + } + + model.addAttribute("companies",companies); return "company/companyList"; } // Affiche les détails d'une entreprise @GetMapping("/companies/view/{id}") - public String viewCompany(@PathVariable("id") Long id, Model model) { + public String viewCompany(@PathVariable("id") int id, Model model,HttpServletRequest request) { Optional<Company> companyOpt = companyService.findById(id); if (companyOpt.isPresent()) { Company company = companyOpt.get(); @@ -102,33 +97,13 @@ public class CompanyController { return "redirect:/companies"; } - - // Enregistre une entreprise avec vérification des doublons et message de succès - @PostMapping("/companies/create") - public String registerCompany(Company company, RedirectAttributes redirectAttributes, Model model) { - Optional<Company> existingCompany = companyService.findByMail(company.getMail()); - - if (existingCompany.isPresent()) { - model.addAttribute("errorMessage", "❌ Une entreprise avec cet e-mail existe déjà !"); - model.addAttribute("company", company); - return "company/companyForm"; // 🔹 Rester sur le formulaire en cas d'erreur - } - - try { - companyService.saveCompany(company); - redirectAttributes.addFlashAttribute("successMessage", "✅ L'entreprise a été ajoutée avec succès !"); - return "redirect:/companies"; - } catch (Exception e) { - model.addAttribute("errorMessage", "❌ Erreur lors de l'inscription !"); - model.addAttribute("company", company); - return "company/companyForm"; // Rester sur le formulaire en cas d'erreur - } - } - // Affiche le formulaire de modification d'une entreprise // Affiche le formulaire de modification d'une entreprise - @GetMapping("/companies/{id}/edit") - public String showEditForm(@PathVariable("id") Long id, Model model, RedirectAttributes redirectAttributes) { + @GetMapping("/companies/edit/{id}") + public String showEditForm(@PathVariable("id") int id, Model model, RedirectAttributes redirectAttributes, HttpServletRequest request) { + if (!CheckAuth.isUserAuthenticated(request.getSession(),id)) { + return "redirect:/login"; + } Optional<Company> companyOpt = companyService.findById(id); if (companyOpt.isPresent()) { @@ -143,10 +118,14 @@ public class CompanyController { // // Met à jour les informations d'une entreprise avec message de succès // Met à jour les informations d'une entreprise avec message de succès @PostMapping("/companies/update") - public String updateCompany(@ModelAttribute Company company, RedirectAttributes redirectAttributes) { + public String updateCompany(@ModelAttribute Company company, RedirectAttributes redirectAttributes,HttpServletRequest request) { + // check user auth + if (!CheckAuth.isUserAuthenticated(request.getSession(),company.getId())) { + return "redirect:/login"; + } try { // Vérifier si l'entreprise existe avant la mise à jour - Optional<Company> existingCompany = companyService.findById(Long.valueOf(company.getId())); + Optional<Company> existingCompany = companyService.findById(company.getId()); if (existingCompany.isPresent()) { // Mettre à jour les informations de l'entreprise @@ -159,7 +138,7 @@ public class CompanyController { // Enregistrer la mise à jour companyService.saveCompany(companyToUpdate); redirectAttributes.addFlashAttribute("successMessage", "✅ L'entreprise a été mise à jour avec succès !"); - return "redirect:/companies/view/" + company.getId(); + return "redirect:/companies" ; } else { redirectAttributes.addFlashAttribute("errorMessage", "❌ L'entreprise n'existe pas !"); return "redirect:/companies"; @@ -177,15 +156,65 @@ public class CompanyController { // Supprime une entreprise // Supprime une entreprise @GetMapping("/companies/delete/{id}") - public String deleteCompany(@PathVariable("id") Long id, RedirectAttributes redirectAttributes) { + public String deleteCompany(@PathVariable("id") int id, RedirectAttributes redirectAttributes,HttpServletRequest request) { + if (!CheckAuth.isUserAuthenticated(request.getSession(),id)) { + return "redirect:/login"; + } try { companyService.deleteCompany(id); + // Invalidation de session si nécessaire + HttpSession session = request.getSession(false); + if (session != null) { + AppUser loggedInUser = (AppUser) session.getAttribute("loggedInUser"); + if (loggedInUser != null && loggedInUser.getId() == id) { + session.invalidate(); + return "redirect:/login?logout"; + } + } redirectAttributes.addFlashAttribute("successMessage", "✅ Entreprise supprimée avec succès !"); } catch (Exception e) { redirectAttributes.addFlashAttribute("errorMessage", "❌ Erreur lors de la suppression !"); } return "redirect:/companies"; } + + @GetMapping("/jobOffers/view/{id}") + public String viewJobOffer(@PathVariable("id") int id, Model model,HttpServletRequest request) { + if (!CheckAuth.isUserAuthenticated(request.getSession(),id)) { + return "redirect:/login"; + } + Optional<JobOffer> offerOpt = jobOfferService.findById(id); + if (offerOpt.isPresent()) { + model.addAttribute("job", offerOpt.get()); + return "jobOffer/jobOfferView"; + } + return "redirect:/companies"; // Redirige si l'offre n'est pas trouvée + } + + + + + + // // // Afficher les offres d'emploi d'une entreprise spécifique + @GetMapping("/companies/{id}/jobOffers") + public String listCompanyJobOffers(@PathVariable("id") int companyId, Model model,HttpServletRequest request) { + if (!CheckAuth.isUserAuthenticated(request.getSession(),companyId)) { + return "redirect:/login"; + } + Optional<Company> companyOpt = companyService.findById(companyId); + if (companyOpt.isPresent()) { + Company company = companyOpt.get(); + List<JobOffer> jobOffers = jobOfferService.findByCompany(company); + model.addAttribute("company", company); + model.addAttribute("jobOffers", jobOffers); + return "company/jobOffers"; + } + + model.addAttribute("errorMessage", "Entreprise introuvable !"); + return "error"; + + + } } diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/controller/JobOfferController.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/controller/JobOfferController.java index b20d086..4a1f016 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/controller/JobOfferController.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/controller/JobOfferController.java @@ -1,11 +1,13 @@ package fr.atlantique.imt.inf211.jobmngt.controller; -import java.util.Date; -import java.util.List; -import java.util.Optional; -import java.util.Set; +import java.time.LocalDate; +import java.util.*; import java.util.stream.Collectors; +import fr.atlantique.imt.inf211.jobmngt.config.CheckAuth; +import fr.atlantique.imt.inf211.jobmngt.entity.*; +import fr.atlantique.imt.inf211.jobmngt.service.*; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -17,54 +19,33 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.mvc.support.RedirectAttributes; -import fr.atlantique.imt.inf211.jobmngt.entity.AppUser; -import fr.atlantique.imt.inf211.jobmngt.entity.JobOffer; -import fr.atlantique.imt.inf211.jobmngt.entity.Sector; -import fr.atlantique.imt.inf211.jobmngt.service.CompanyService; -import fr.atlantique.imt.inf211.jobmngt.service.JobOfferService; -import fr.atlantique.imt.inf211.jobmngt.service.QualificationLevelService; -import fr.atlantique.imt.inf211.jobmngt.service.SectorService; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; @Controller @RequestMapping("/jobs") +@RequiredArgsConstructor public class JobOfferController { - private final JobOfferService jobOfferService; - private final CompanyService companyService; - private final QualificationLevelService qualificationLevelService; - private final SectorService sectorService; - - @Autowired - public JobOfferController(JobOfferService jobOfferService, - CompanyService companyService, - QualificationLevelService qualificationLevelService, - SectorService sectorService) { - this.jobOfferService = jobOfferService; - this.companyService = companyService; - this.qualificationLevelService = qualificationLevelService; - this.sectorService = sectorService; - } + final JobOfferService jobOfferService; + final CompanyService companyService; + final QualificationLevelService qualificationLevelService; + final SectorService sectorService; + final MatchingIndexService matchingIndexService; + + // Affiche la liste des offres d'emploi avec log @GetMapping public String listJobOffers(Model model, HttpServletRequest request) { - HttpSession session = request.getSession(); - AppUser loggedInUser = (AppUser) session.getAttribute("user"); - - // Vérification de l'utilisateur - if (loggedInUser != null) { - model.addAttribute("userType", loggedInUser.getUsertype()); - model.addAttribute("userEmail", loggedInUser.getMail()); - System.out.println("Utilisateur connecté : " + loggedInUser.getMail() + " | Type: " + loggedInUser.getUsertype()); - } else { - model.addAttribute("userType", null); - model.addAttribute("userEmail", null); - System.out.println("Aucun utilisateur connecté."); + + List<JobOffer> jobOffers = jobOfferService.getAllJobOffers(); + if (jobOffers.isEmpty()) { + model.addAttribute("errorMessage", "Aucune offre trouvée."); } - model.addAttribute("jobOffers", jobOfferService.getAllJobOffers()); + + model.addAttribute("jobOffers", jobOffers); return "jobOffer/jobOfferList"; // Correspond au fichier "jobOfferList.html" dans /templates/ } @@ -73,12 +54,13 @@ public class JobOfferController { @GetMapping("/view/{id}") - public String viewJobOffer(@PathVariable("id") Long id, Model model, RedirectAttributes redirectAttributes) { + public String viewJobOffer(@PathVariable("id") int id, Model model, RedirectAttributes redirectAttributes, + HttpServletRequest request) { System.out.println("🔍 Recherche de l'offre avec ID : " + id); Optional<JobOffer> jobOfferOpt = jobOfferService.findById(id); if (jobOfferOpt.isPresent()) { - model.addAttribute("jobOffer", jobOfferOpt.get()); + model.addAttribute("job", jobOfferOpt.get()); return "jobOffer/jobOfferView"; } else { System.out.println("❌ L'offre avec ID " + id + " est introuvable en base de données !"); @@ -88,8 +70,9 @@ public class JobOfferController { } - @GetMapping("/company/job/view/{id}") - public String viewCompanyJob(@PathVariable("id") Long jobId, Model model) { + /* @GetMapping("/company/job/view/{id}") + public String viewCompanyJob(@PathVariable("id") int jobId, Model model) { + Optional<JobOffer> jobOffer = jobOfferService.findById(jobId); if (jobOffer.isEmpty()) { @@ -98,7 +81,7 @@ public class JobOfferController { model.addAttribute("jobOffer", jobOffer.get()); return "jobOffer/companyJobOfferView"; // Nouveau template dédié - } + }*/ @@ -107,102 +90,73 @@ public class JobOfferController { // Affiche le formulaire de création avec sélection des secteurs @GetMapping("/create") public String showCreateForm(Model model, HttpServletRequest request) { - -// // Vérifier si l'utilisateur est une entreprise -// HttpSession session = request.getSession(false); -// if (session == null || !"company".equals(session.getAttribute("usertype"))) { -// return "redirect:/login"; // Redirige vers la connexion si ce n'est pas une entreprise -// } - + // Vérification authentification + if (!CheckAuth.isUserAuthenticated(request.getSession())) { + return "redirect:/login"; + } JobOffer jobOffer = new JobOffer(); - jobOffer.setPublicationDate(new Date()); + jobOffer.setPublicationDate(LocalDate.now()); model.addAttribute("jobOffer", jobOffer); - model.addAttribute("companies", companyService.getAllCompanies()); model.addAttribute("qualificationLevels", qualificationLevelService.getAllQualificationLevels()); model.addAttribute("sectors", sectorService.listOfSectors()); return "jobOffer/jobOfferForm"; } - // Enregistre une offre avec gestion des secteurs et des erreurs @PostMapping("/save") public String saveJobOffer(@ModelAttribute JobOffer jobOffer, @RequestParam(value = "sectorIds", required = false) List<Integer> sectorIds, - RedirectAttributes redirectAttributes) { - try { + RedirectAttributes redirectAttributes,HttpServletRequest request) { + // check user auth + if (!CheckAuth.isUserAuthenticated(request.getSession())) { + return "redirect:/login"; + } + Optional<Company> companyOpt = companyService.findById((Integer) request.getSession().getAttribute("userId")); + if (companyOpt.isEmpty()) { + return "redirect:/login"; + } System.out.println("🔹 Tentative d'enregistrement: " + jobOffer); // Associer les secteurs sélectionnés à l'offre d'emploi if (sectorIds != null && !sectorIds.isEmpty()) { - Set<Sector> selectedSectors = sectorIds.stream() - .map(id -> sectorService.findById(id.longValue())) - .filter(Optional::isPresent) - .map(Optional::get) - .collect(Collectors.toSet()); // Convertir la liste en Set + Collection<Sector> selectedSectors =sectorService.getSectorsByIds(sectorIds); // Convertir la liste en Set jobOffer.setSectors(selectedSectors); // Affectation corrigée } - - jobOfferService.saveJobOffer(jobOffer); - System.out.println(" Enregistrement réussi !"); - redirectAttributes.addFlashAttribute("successMessage", " Offre d'emploi créée avec succès !"); - return "redirect:/jobs"; - } catch (Exception e) { + jobOffer.setCompany(companyOpt.get()); + try { + jobOfferService.saveJobOffer(jobOffer); + matchingIndexService.indexJobOffer(jobOffer); // Indexation après sauvegarde + + redirectAttributes.addFlashAttribute("successMessage", "Offre d'emploi créée avec succès !"); + return "redirect:/jobs"; + } catch (Exception e) { System.err.println(" Erreur lors de l'enregistrement: " + e.getMessage()); redirectAttributes.addFlashAttribute("errorMessage", " Erreur lors de la création de l'offre !"); return "redirect:/jobs/create"; } + } - /* @PostMapping("/save") - public String saveJobOffer(@ModelAttribute JobOffer jobOffer, - @RequestParam(value = "sectorIds", required = false) List<Long> sectorIds, - RedirectAttributes redirectAttributes, - HttpServletRequest request ) { - - // Vérifier si l'utilisateur est une entreprise - // HttpSession session = request.getSession(false); - // if (session == null || !"company".equals(session.getAttribute("usertype"))) { - // return "redirect:/login"; - // } - try { - if (sectorIds != null && !sectorIds.isEmpty()) { - Set<Sector> selectedSectors = sectorIds.stream() - .map(id -> sectorService.findById(id.intValue())) // - .filter(Optional::isPresent) - .map(Optional::get) - .collect(Collectors.toSet()); - jobOffer.setSectors(selectedSectors); - } - jobOfferService.saveJobOffer(jobOffer); - redirectAttributes.addFlashAttribute("successMessage", "✅ Offre créée avec succès !"); - return "redirect:/jobs"; - } catch (Exception e) { - redirectAttributes.addFlashAttribute("errorMessage", "❌ Erreur lors de la création de l'offre !"); - return "redirect:/jobs/create"; - } - }*/ @GetMapping("/delete/{id}") - public String deleteJobOffer(@PathVariable("id") Long id, HttpServletRequest request, RedirectAttributes redirectAttributes) { + public String deleteJobOffer(@PathVariable("id") int id, HttpServletRequest request, RedirectAttributes redirectAttributes) { + -// Supprime une offre d'emploi -/*@GetMapping("/delete/{id}") -public String deleteJobOffer(@PathVariable("id") int id, RedirectAttributes redirectAttributes) {*/ + // check user auth + if (!CheckAuth.isUserAuthenticated(request.getSession())) { + return "redirect:/login"; + } Optional<JobOffer> jobOfferOpt = jobOfferService.findById(id); if (jobOfferOpt.isPresent()) { - HttpSession session = request.getSession(false); - String userType = (session != null) ? (String) session.getAttribute("usertype") : null; - String userEmail = (session != null) ? (String) session.getAttribute("useremail") : null; JobOffer jobOffer = jobOfferOpt.get(); // Vérifie si l'utilisateur est bien une entreprise et le propriétaire de l'offre - if (userType != null && userType.equals("company") && jobOffer.getCompany().getMail().equals(userEmail)) { try { jobOfferService.deleteJobOffer(id); redirectAttributes.addFlashAttribute("successMessage", "✅ Offre supprimée avec succès !"); @@ -212,9 +166,6 @@ public String deleteJobOffer(@PathVariable("id") int id, RedirectAttributes redi } else { redirectAttributes.addFlashAttribute("errorMessage", "❌ Vous n'avez pas l'autorisation de supprimer cette offre !"); } - } else { - redirectAttributes.addFlashAttribute("errorMessage", "❌ L'offre avec ID " + id + " n'existe pas !"); - } return "redirect:/jobs"; } /* try { @@ -232,20 +183,83 @@ public String deleteJobOffer(@PathVariable("id") int id, RedirectAttributes redi // Affiche le formulaire de modification - @GetMapping("/{id}/edit") - public String showEditForm(@PathVariable("id") Long id, Model model, RedirectAttributes redirectAttributes) { + @GetMapping("/edit/{id}") + public String showEditForm(@PathVariable int id, Model model, HttpSession session) { + if (!CheckAuth.isUserAuthenticated(session)) { + return "redirect:/login"; + } + + Optional<JobOffer> jobOfferOpt = jobOfferService.findById(id); - if (jobOfferOpt.isPresent()) { - model.addAttribute("jobOffer", jobOfferOpt.get()); - model.addAttribute("companies", companyService.getAllCompanies()); - model.addAttribute("qualificationLevels", qualificationLevelService.getAllQualificationLevels()); - model.addAttribute("sectors", sectorService.listOfSectors()); - return "jobOffer/jobOfferForm"; + if (jobOfferOpt.isEmpty()) { + model.addAttribute("error", "Candidature non trouvée."); + return "error"; + } + + model.addAttribute("job", jobOfferOpt.get()); + model.addAttribute("qualificationLevels", qualificationLevelService.getAllQualificationLevels()); + model.addAttribute("sectors", sectorService.listOfSectors()); + + return "jobOffer/jobOfferEdit.html"; + } + + @PostMapping("/update") + public String updateJobOffer( + @RequestParam("id") int id, + @RequestParam("title") String title, + @RequestParam("publicationDate") LocalDate date, + @RequestParam("taskDescription") String description, + @RequestParam("qualificationLevel") int qualificationLevelId, + @RequestParam(value = "sectorIds", required = false) List<Integer> sectorIds, + RedirectAttributes redirectAttributes, + HttpServletRequest request) { + + // Vérification d'authentification + if (!CheckAuth.isUserAuthenticated(request.getSession())) { + return "redirect:/login"; + } + + // Récupération de l'offre existante + Optional<JobOffer> jobOfferOpt = jobOfferService.findById(id); + if (jobOfferOpt.isEmpty()) { + redirectAttributes.addFlashAttribute("error", "Offre d'emploi non trouvée."); + return "redirect:/jobs"; + } + + JobOffer jobOffer = jobOfferOpt.get(); + + // Mise à jour des champs de base + jobOffer.setTitle(title); + jobOffer.setTaskDescription(description); + jobOffer.setPublicationDate(date); + + // Mise à jour du niveau de qualification + Optional<QualificationLevel> qualificationLevel = qualificationLevelService.findById(qualificationLevelId); + qualificationLevel.ifPresent(jobOffer::setQualificationLevel); + + // Mise à jour des secteurs + if (sectorIds != null && !sectorIds.isEmpty()) { + Collection<Sector> selectedSectors = sectorService.getSectorsByIds(sectorIds); + jobOffer.setSectors(selectedSectors); } else { - redirectAttributes.addFlashAttribute("errorMessage", "L'offre d'emploi avec ID " + id + " n'existe pas !"); + jobOffer.setSectors(new HashSet<>()); // Effacer les secteurs si aucun n'est sélectionné + } + + try { + // Sauvegarde de l'offre mise à jour + jobOfferService.saveJobOffer(jobOffer); + + // Mise à jour de l'index de matching si nécessaire + matchingIndexService.updateIndex(jobOffer,true); + + redirectAttributes.addFlashAttribute("successMessage", "Offre mise à jour avec succès !"); return "redirect:/jobs"; + } catch (Exception e) { + redirectAttributes.addFlashAttribute("errorMessage", "Erreur lors de la mise à jour de l'offre: " + e.getMessage()); + return "redirect:/jobs/edit/" + id; } } + } diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/controller/LoginController.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/controller/LoginController.java index d18397b..dc3e029 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/controller/LoginController.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/controller/LoginController.java @@ -2,71 +2,110 @@ package fr.atlantique.imt.inf211.jobmngt.controller; import java.util.Optional; +import fr.atlantique.imt.inf211.jobmngt.dao.AppUserDao; +import fr.atlantique.imt.inf211.jobmngt.dao.CandidateDao; +import fr.atlantique.imt.inf211.jobmngt.dao.CompanyDao; +import fr.atlantique.imt.inf211.jobmngt.entity.Candidate; +import fr.atlantique.imt.inf211.jobmngt.entity.Company; +import fr.atlantique.imt.inf211.jobmngt.entity.RoleType; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.ModelAndView; import fr.atlantique.imt.inf211.jobmngt.entity.AppUser; import fr.atlantique.imt.inf211.jobmngt.service.AppUserService; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; @Controller +@RequiredArgsConstructor public class LoginController { - // Value injected from the application.properties file - @Value("${jobmngt.admin}") - private String adminLogin; - @Autowired - private AppUserService userAppService; + final AppUserDao appUserDao; + final CompanyDao companyDao; + final CandidateDao candidateDao; - // Login form Get Method - @RequestMapping(value = "/login", method = RequestMethod.GET) - public String login() { + final PasswordEncoder passwordEncoder; + + // GET /login - Affiche le formulaire + @GetMapping("/login") + public String showLoginForm( + @RequestParam(required = false) String error, + @RequestParam(required = false) String logout, + Model model) { + + if (error != null) { + model.addAttribute("error", "Email ou mot de passe incorrect"); + } + if (logout != null) { + model.addAttribute("message", "Vous avez été déconnecté avec succès"); + } return "login"; } - - - // Login form post method - @RequestMapping(value = "/login", method = RequestMethod.POST) - public ModelAndView checkLog(@RequestParam("mail") String mail, @RequestParam("password") String pwd, - HttpServletRequest request) { - HttpSession session = request.getSession(); - AppUser appUser = new AppUser(); - appUser.setMail(mail); - appUser.setPassword(pwd); - Optional<AppUser> user = userAppService.checkLogin(appUser); - if (user.isPresent()) { - appUser = user.get(); - System.out.println("User found uid: " + appUser.getId()); - session.setAttribute("loggedInUser", appUser); - if (appUser.getUsertype().equals("candidate")) - session.setAttribute("usertype", "candidate"); - else - session.setAttribute("usertype", "company"); - - session.setAttribute("uid", appUser.getId()); - return new ModelAndView("redirect:/"); - } else { - ModelAndView mav = new ModelAndView("login"); - mav.addObject("error", "Password or username incorrect."); - return mav; + // POST /login - Traite la connexion + @PostMapping("/login") + public String processLogin( + @RequestParam String mail, + @RequestParam String password, + HttpServletRequest request, + RedirectAttributes redirectAttributes) { + + Optional<AppUser> userOpt = appUserDao.findByMail(mail); + if (userOpt.isEmpty()){ + redirectAttributes.addFlashAttribute("error", "User not found"); + return "redirect:/login"; + + } + // Vérification des identifiants + if (!passwordEncoder.matches(password, userOpt.get().getPassword())) { + redirectAttributes.addFlashAttribute("error", "Email ou mot de passe incorrect"); + return "redirect:/login"; + } + + // Création de la session + AppUser user = userOpt.get(); + HttpSession session = request.getSession(true); + session.setAttribute("userId", user.getId()); + session.setAttribute("userMail", user.getMail()); + session.setAttribute("userType", user.getUsertype()); + if(user.getUsertype().equals(RoleType.Company.name())){ + Company company = companyDao.findByMail(user.getMail()).get(); + if(company == null){ + redirectAttributes.addFlashAttribute("error", "company not found"); + + } + session.setAttribute("loggedInUser", company); + } + else { + Candidate candidate = candidateDao.findById(user.getId()); + if (candidate==null){ + redirectAttributes.addFlashAttribute("error", "candidate not found"); + + } + session.setAttribute("loggedInUser", candidate); } - } - @RequestMapping(value = "/logout", method = RequestMethod.GET) - public String logout(HttpServletRequest request) { - HttpSession session = request.getSession(); - session.setAttribute("uid", null); - session.setAttribute("user", null); return "redirect:/"; } + // GET /logout - Déconnexion + @GetMapping("/logout") + public String logout(HttpServletRequest request) { + HttpSession session = request.getSession(false); + if (session != null) { + session.invalidate(); + } + return "redirect:/login?logout"; + } } \ No newline at end of file diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/converter/CompanyConverter.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/converter/CompanyConverter.java index 9dd7810..1a81220 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/converter/CompanyConverter.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/converter/CompanyConverter.java @@ -20,7 +20,7 @@ public class CompanyConverter implements Converter<String, Company> { if (source == null || source.isEmpty()) { return null; } - return companyService.findById(Long.parseLong(source)).orElse(null); + return companyService.findById(Integer.parseInt(source)).orElse(null); } } diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/dao/AppUserDao.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/dao/AppUserDao.java index 297c3a9..7afa498 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/dao/AppUserDao.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/dao/AppUserDao.java @@ -68,9 +68,9 @@ public class AppUserDao { } @Transactional(readOnly = true) - public Long count() { + public int count() { String query = "SELECT COUNT(u) FROM AppUser u"; - return entityManager.createQuery(query, Long.class).getSingleResult(); + return entityManager.createQuery(query, int.class).getSingleResult(); } @Transactional(readOnly = true) diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/dao/ApplicationDao.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/dao/ApplicationDao.java index c9353c6..9a5edb2 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/dao/ApplicationDao.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/dao/ApplicationDao.java @@ -1,5 +1,6 @@ package fr.atlantique.imt.inf211.jobmngt.dao; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.logging.Level; @@ -12,6 +13,7 @@ import fr.atlantique.imt.inf211.jobmngt.entity.Application; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import jakarta.persistence.TypedQuery; +import org.thymeleaf.expression.Lists; @Repository public class ApplicationDao { @@ -52,6 +54,34 @@ public class ApplicationDao { TypedQuery<Application> query = entityManager.createQuery("SELECT a FROM Application a ORDER BY a.appdate DESC", Application.class); return query.getResultList(); } + @Transactional(readOnly = true) + public List<Application> findByCandidateId(int candidateId) { + logger.log(Level.INFO, "Fetching applications for candidate ID: {0}", candidateId); + TypedQuery<Application> query = entityManager.createQuery( + "SELECT a FROM Application a WHERE a.candidate.id = :candidateId ORDER BY a.appdate DESC", + Application.class); + query.setParameter("candidateId", candidateId); + return query.getResultList(); + } + @Transactional(readOnly = true) + public boolean existsByCandidateAndJobOffer(Integer candidateId, Integer jobOfferId) { + TypedQuery<Long> query = entityManager.createQuery( + "SELECT COUNT(a) FROM Application a WHERE a.candidate.id = :candidateId AND a.jobOffer.id = :jobOfferId", + Long.class); + query.setParameter("candidateId", candidateId); + query.setParameter("jobOfferId", jobOfferId); + return query.getSingleResult() > 0; + } + + @Transactional(readOnly = true) + public List<Application> findByJobOfferId(Integer jobOfferId) { + /* TypedQuery<Application> query = entityManager.createQuery( + "SELECT a FROM Application a WHERE a.jobOffer.id = :jobOfferId ORDER BY a.appDate DESC", + Application.class); + query.setParameter("jobOfferId", jobOfferId); + return query.getResultList();*/ + return Collections.emptyList(); + } } diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/dao/CandidateDao.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/dao/CandidateDao.java index 99a93f1..49da5f9 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/dao/CandidateDao.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/dao/CandidateDao.java @@ -1,6 +1,7 @@ package fr.atlantique.imt.inf211.jobmngt.dao; import fr.atlantique.imt.inf211.jobmngt.entity.Candidate; +import fr.atlantique.imt.inf211.jobmngt.entity.Company; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import jakarta.persistence.TypedQuery; @@ -17,8 +18,13 @@ public class CandidateDao { private EntityManager entityManager; @Transactional - public void persist(Candidate candidate) { - entityManager.persist(candidate); + public Candidate save(Candidate candidate) { + if (candidate.getId() == 0) { + entityManager.persist(candidate); + return candidate; + } else { + return entityManager.merge(candidate); + } } @Transactional diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/dao/CompanyDao.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/dao/CompanyDao.java index f08c8b2..d82258a 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/dao/CompanyDao.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/dao/CompanyDao.java @@ -32,13 +32,13 @@ public class CompanyDao { } @Transactional(readOnly = true) - public Optional<Company> findById(Long id) { // ✅ Changer int en Long + public Optional<Company> findById(int id) { // ✅ Changer int en Long return Optional.ofNullable(entityManager.find(Company.class, id)); } @Transactional -public void remove(Long id) { +public void remove(int id) { Company company = findById(id).orElseThrow(() -> new RuntimeException("Impossible de supprimer : l'entreprise avec ID " + id + " n'existe pas.")); entityManager.remove(company); diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/dao/JobOfferDao.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/dao/JobOfferDao.java index 43676b7..be01c5e 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/dao/JobOfferDao.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/dao/JobOfferDao.java @@ -11,7 +11,7 @@ import fr.atlantique.imt.inf211.jobmngt.entity.Company; import fr.atlantique.imt.inf211.jobmngt.entity.JobOffer; @Repository -public interface JobOfferDao extends JpaRepository<JobOffer, Long> { // ✅ Hérite maintenant de JpaRepository +public interface JobOfferDao extends JpaRepository<JobOffer, Integer> { // ✅ Hérite maintenant de JpaRepository @Query("SELECT DISTINCT j FROM JobOffer j " + "JOIN j.sectors s " + @@ -19,13 +19,13 @@ public interface JobOfferDao extends JpaRepository<JobOffer, Long> { // ✅ Hér "WHERE LOWER(s.label) LIKE LOWER(:sectorLabel) " + "AND LOWER(q.label) LIKE LOWER(:qualificationLevel)") List<JobOffer> findBySectorAndQualification(String sectorLabel, String qualificationLevel); -int countByCompanyId(Long companyId); -List<JobOffer> findByCompanyId(Long companyId); - List<JobOffer> findByCompany(Optional<Company> company); +Integer countByCompanyId(int companyId); +List<JobOffer> findByCompanyId(int companyId); +List<JobOffer> findByCompany(Optional<Company> company); + - } diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/dao/QualificationLevelDao.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/dao/QualificationLevelDao.java index 3e0ec38..c0e0c52 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/dao/QualificationLevelDao.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/dao/QualificationLevelDao.java @@ -47,10 +47,10 @@ public class QualificationLevelDao { } @Transactional(readOnly = true) - public Long count() { + public int count() { logger.log(Level.INFO, "Counting all qualification levels"); String query = "SELECT COUNT(q) FROM QualificationLevel q"; - return entityManager.createQuery(query, Long.class).getSingleResult(); + return entityManager.createQuery(query, int.class).getSingleResult(); } @Transactional(readOnly = true) @@ -61,11 +61,11 @@ public class QualificationLevelDao { } @Transactional(readOnly = true) - public List<QualificationLevel> findByLabel(String label) { - logger.log(Level.INFO, "Fetching qualification levels with label: " + label); - String jpql = "SELECT q FROM QualificationLevel q WHERE q.level = :label"; - TypedQuery<QualificationLevel> query = entityManager.createQuery(jpql, QualificationLevel.class); - query.setParameter("label", label); - return query.getResultList(); + public boolean existsByLabel(String label) { + String query = "SELECT COUNT(q) FROM QualificationLevel q WHERE q.label = :label"; + Long count = entityManager.createQuery(query, Long.class) + .setParameter("label", label) + .getSingleResult(); + return count > 0; } } diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/dao/SectorDao.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/dao/SectorDao.java index 9cc968d..6fde34b 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/dao/SectorDao.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/dao/SectorDao.java @@ -1,6 +1,8 @@ package fr.atlantique.imt.inf211.jobmngt.dao; +import java.util.Collection; import java.util.List; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -45,13 +47,13 @@ public class SectorDao { } @Transactional(readOnly = true) - public Long count() { + public int count() { logger.log(Level.INFO, "Counting all sectors"); String query = "SELECT COUNT(s) FROM Sector s"; - return entityManager.createQuery(query, Long.class).getSingleResult(); + return entityManager.createQuery(query, Integer.class).getSingleResult(); } - public List<Sector> findAll(String sort, String order) { + public Collection<Sector> findAll(String sort, String order) { if (order == null || (!order.equalsIgnoreCase("ASC") && !order.equalsIgnoreCase("DESC"))) { order = "ASC"; // Définit une valeur par défaut si `order` est null } @@ -72,16 +74,17 @@ public class SectorDao { * ✅ Nouvelle surcharge de findAll() sans paramètres. */ @Transactional(readOnly = true) - public List<Sector> findAll() { + public Collection<Sector> findAll() { logger.log(Level.INFO, "Fetching all sectors"); return entityManager.createQuery("SELECT s FROM Sector s", Sector.class).getResultList(); } + /** * ✅ Nouvelle méthode pour récupérer plusieurs secteurs par ID. */ @Transactional(readOnly = true) - public List<Sector> findAllById(List<Integer> ids) { + public Collection<Sector> findAllById(List<Integer> ids) { logger.log(Level.INFO, "Fetching sectors with IDs: " + ids); return entityManager.createQuery("SELECT s FROM Sector s WHERE s.id IN :ids", Sector.class) .setParameter("ids", ids) @@ -119,7 +122,7 @@ public class SectorDao { } @Transactional(readOnly = true) - public List<Sector> findAllByIds(List<Integer> ids) { + public Collection<Sector> findAllByIds(List<Integer> ids) { logger.log(Level.INFO, "Fetching sectors with IDs: " + ids); String jpql = "SELECT s FROM Sector s WHERE s.id IN :ids"; @@ -127,8 +130,15 @@ public class SectorDao { TypedQuery<Sector> query = entityManager.createQuery(jpql, Sector.class); query.setParameter("ids", ids); - return query.getResultList(); + return query.getResultList(); + } + @Transactional(readOnly = true) + public boolean existsByLabel(String label) { + String query = "SELECT COUNT(s) FROM Sector s WHERE s.label = :label"; + Long count = entityManager.createQuery(query, Long.class) + .setParameter("label", label) + .getSingleResult(); + return count > 0; } - } diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/entity/AppUser.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/entity/AppUser.java index 7c0503d..e4b458c 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/entity/AppUser.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/entity/AppUser.java @@ -1,18 +1,23 @@ package fr.atlantique.imt.inf211.jobmngt.entity; import java.io.Serializable; +import java.util.Collection; +import java.util.Collections; import com.fasterxml.jackson.annotation.JsonIdentityInfo; import com.fasterxml.jackson.annotation.ObjectIdGenerators; import jakarta.persistence.*; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; @Entity @Table(name = "appuser", schema = "public", uniqueConstraints = @UniqueConstraint(columnNames = "mail")) @Inheritance(strategy = InheritanceType.JOINED) // Stratégie d'héritage @DiscriminatorColumn(name = "usertype", discriminatorType = DiscriminatorType.STRING) @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") -public class AppUser implements Serializable { +public class AppUser implements Serializable, UserDetails { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) // Auto-incrémentation @@ -27,9 +32,8 @@ public class AppUser implements Serializable { @Column(name = "city", length = 30) private String city; - @Enumerated(EnumType.STRING) @Column(name = "usertype", length = 50, insertable = false, updatable = false) - private Role usertype; // Permet d'identifier le type d'utilisateur + private String usertype; // Permet d'identifier le type d'utilisateur // Constructeurs public AppUser() {} @@ -58,10 +62,40 @@ public class AppUser implements Serializable { this.mail = mail; } + @Override + public Collection<? extends GrantedAuthority> getAuthorities() { + return Collections.singletonList(new SimpleGrantedAuthority("role" + this.usertype.toUpperCase())); + } + public String getPassword() { return password; } + @Override + public String getUsername() { + return this.mail; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + public void setPassword(String password) { this.password = password; } @@ -74,11 +108,11 @@ public class AppUser implements Serializable { this.city = city; } - public Role getUsertype() { + public String getUsertype() { return usertype; } - public void setUsertype(Role usertype) { + public void setUsertype(String usertype) { this.usertype = usertype; } } diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/entity/Application.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/entity/Application.java index 7f58579..11e97bb 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/entity/Application.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/entity/Application.java @@ -1,8 +1,12 @@ package fr.atlantique.imt.inf211.jobmngt.entity; import java.io.Serializable; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.Collection; +import java.util.HashSet; import java.util.List; +import java.util.Set; import com.fasterxml.jackson.annotation.JsonIdentityInfo; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -35,7 +39,6 @@ public class Application implements Serializable { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "candidateid", nullable = false) - @JsonIgnoreProperties({"applications"}) // ✅ Évite la surcharge JSON circulaire private Candidate candidate; @ManyToOne(fetch = FetchType.LAZY) @@ -47,7 +50,7 @@ public class Application implements Serializable { private String cv; @Column(name = "appdate") - private LocalDateTime appdate; + private LocalDate appdate; @ManyToMany(fetch = FetchType.LAZY) @JoinTable( @@ -57,7 +60,7 @@ public class Application implements Serializable { inverseJoinColumns = { @JoinColumn(name = "sectorid", nullable = false, updatable = false) } ) @JsonIgnoreProperties({"applications"}) // ✅ Empêche la surcharge JSON circulaire - private List<Sector> sectors; + private Collection<Sector> sectors = new HashSet<>();; // Constructeurs public Application() {} @@ -82,9 +85,10 @@ public class Application implements Serializable { public String getCv() { return cv; } public void setCv(String cv) { this.cv = cv; } - public LocalDateTime getAppdate() { return appdate; } - public void setAppdate(LocalDateTime appdate) { this.appdate = appdate; } + public LocalDate getAppdate() { return appdate; } + public void setAppdate(LocalDate appdate) { this.appdate = appdate; } + + public Collection<Sector> getSectors() { return sectors; } + public void setSectors(Collection<Sector> sectors) { this.sectors = sectors; } - public List<Sector> getSectors() { return sectors; } - public void setSectors(List<Sector> sectors) { this.sectors = sectors; } } diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/entity/Candidate.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/entity/Candidate.java index 8a200c2..54464ac 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/entity/Candidate.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/entity/Candidate.java @@ -1,11 +1,12 @@ package fr.atlantique.imt.inf211.jobmngt.entity; import java.io.Serializable; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.PrimaryKeyJoinColumn; -import jakarta.persistence.Table; +import com.fasterxml.jackson.annotation.JsonIgnore; +import jakarta.persistence.*; @Entity @Table(name = "candidate", schema = "public") @@ -18,6 +19,10 @@ public class Candidate extends AppUser implements Serializable { @Column(name = "firstname", nullable = false, length = 50) private String firstname; + @OneToMany(mappedBy = "candidate", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) + @JsonIgnore // Évite la surcharge JSON circulaire + private Collection<Application> applications; + // Constructeurs public Candidate() {} @@ -25,6 +30,7 @@ public class Candidate extends AppUser implements Serializable { super(mail, password, city); this.lastname = lastname; this.firstname = firstname; + this.applications=new HashSet<>(); } // Getters et Setters @@ -43,4 +49,15 @@ public class Candidate extends AppUser implements Serializable { public void setFirstname(String firstname) { this.firstname = firstname; } + public Integer getCandidatAppCount() { + return Math.toIntExact((applications != null) ? (Integer) applications.size() : 0L); + } + public Collection<Application> getCandidatApplications() { + return applications; + } + + public void setCandidatApplications(Set<Application> candidatApplications) { + this.applications = candidatApplications; + } + } diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/entity/Company.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/entity/Company.java index 4688138..0e4593f 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/entity/Company.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/entity/Company.java @@ -69,8 +69,8 @@ public class Company extends AppUser implements Serializable { this.jobOffers = jobOffers; } - public long getJobOfferCount() { - return (jobOffers != null) ? (long) jobOffers.size() : 0L; + public Integer getJobOfferCount() { + return Math.toIntExact((jobOffers != null) ? (Integer) jobOffers.size() : 0L); } // public int getJobOfferCount() { diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/entity/JobOffer.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/entity/JobOffer.java index ac444ec..46123eb 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/entity/JobOffer.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/entity/JobOffer.java @@ -1,8 +1,7 @@ package fr.atlantique.imt.inf211.jobmngt.entity; -import java.util.Date; -import java.util.HashSet; -import java.util.Set; +import java.time.LocalDate; +import java.util.*; import org.springframework.format.annotation.DateTimeFormat; @@ -33,7 +32,7 @@ public class JobOffer implements java.io.Serializable { @SequenceGenerator(name = "JOBOFFER_ID_GENERATOR", sequenceName = "JOBOFFER_ID_SEQ", allocationSize = 1) @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "JOBOFFER_ID_GENERATOR") @Column(name="id", unique = true, nullable = false) - private Long id; // ✅ Correct + private int id; // ✅ Correct @ManyToOne(fetch = FetchType.LAZY) @@ -53,7 +52,7 @@ public class JobOffer implements java.io.Serializable { @Temporal(TemporalType.DATE) @DateTimeFormat(pattern = "yyyy-MM-dd") @Column(name="publicationdate") - private Date publicationDate; + private LocalDate publicationDate; @ManyToMany(fetch = FetchType.LAZY) // @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @@ -61,7 +60,7 @@ public class JobOffer implements java.io.Serializable { joinColumns = { @JoinColumn(name="jobofferid", nullable = false, updatable = false) }, inverseJoinColumns = { @JoinColumn(name="sectorid", nullable = false, updatable = false) }) @JsonIgnoreProperties({"applications", "jobOffers"}) // Empêche les boucles JSON infinies - private Set<Sector> sectors = new HashSet<>(); + private Collection<Sector> sectors = new HashSet<>(); // @OneToMany(fetch = FetchType.LAZY, mappedBy = "jobOffer", cascade = CascadeType.ALL) @OneToMany(fetch = FetchType.LAZY, mappedBy = "jobOffer") // Correction du mappedBy @@ -69,15 +68,15 @@ public class JobOffer implements java.io.Serializable { public JobOffer() {} - public JobOffer(Long id, Company company, QualificationLevel qualificationLevel, String title) { + public JobOffer(int id, Company company, QualificationLevel qualificationLevel, String title) { this.id = id; this.company = company; this.qualificationLevel = qualificationLevel; this.title = title; } - public JobOffer(Long id, Company company, QualificationLevel qualificationLevel, String title, - String taskDescription, Date publicationDate, Set<Sector> sectors, Set<OfferMessage> offerMessages) { + public JobOffer(int id, Company company, QualificationLevel qualificationLevel, String title, + String taskDescription, LocalDate publicationDate, Set<Sector> sectors, Set<OfferMessage> offerMessages) { this.id = id; this.company = company; this.qualificationLevel = qualificationLevel; @@ -88,8 +87,8 @@ public class JobOffer implements java.io.Serializable { this.offerMessages = offerMessages; } - public Long getId() { return id; } - public void setId(Long id) { this.id = id; } + public int getId() { return id; } + public void setId(int id) { this.id = id; } public Company getCompany() { return company; } @@ -104,11 +103,11 @@ public class JobOffer implements java.io.Serializable { public String getTaskDescription() { return taskDescription; } public void setTaskDescription(String taskDescription) { this.taskDescription = taskDescription; } - public Date getPublicationDate() { return publicationDate; } - public void setPublicationDate(Date publicationDate) { this.publicationDate = publicationDate; } + public LocalDate getPublicationDate() { return publicationDate; } + public void setPublicationDate(LocalDate publicationDate) { this.publicationDate = publicationDate; } - public Set<Sector> getSectors() { return sectors; } - public void setSectors(Set<Sector> sectors) { this.sectors = sectors; } + public Collection<Sector> getSectors() { return sectors; } + public void setSectors(Collection<Sector> sectors) { this.sectors = sectors; } public Set<OfferMessage> getOfferMessages() { return offerMessages; } public void setOfferMessages(Set<OfferMessage> offerMessages) { this.offerMessages = offerMessages; } diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/entity/Role.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/entity/RoleType.java similarity index 50% rename from src/main/java/fr/atlantique/imt/inf211/jobmngt/entity/Role.java rename to src/main/java/fr/atlantique/imt/inf211/jobmngt/entity/RoleType.java index 2b3e13d..85ec681 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/entity/Role.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/entity/RoleType.java @@ -1,6 +1,6 @@ package fr.atlantique.imt.inf211.jobmngt.entity; -public enum Role { - COMPANY, - CONDIDATE +public enum RoleType { + Company, + Candidate } diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/AppUserService.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/AppUserService.java index 0c810c4..6c7af14 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/AppUserService.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/AppUserService.java @@ -10,7 +10,7 @@ public interface AppUserService { public AppUser getUserapp(Integer id); - public Long nbUsers(); + public int nbUsers(); public Optional<AppUser> checkLogin(AppUser u); } diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/AppUserServiceImpl.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/AppUserServiceImpl.java index c498d20..5c6cb62 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/AppUserServiceImpl.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/AppUserServiceImpl.java @@ -23,7 +23,7 @@ public class AppUserServiceImpl implements AppUserService { return appUserDao.findById(id); } - public Long nbUsers() { + public int nbUsers() { return appUserDao.count(); } diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/CandidateService.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/CandidateService.java index 5ce638c..67312eb 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/CandidateService.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/CandidateService.java @@ -1,6 +1,9 @@ package fr.atlantique.imt.inf211.jobmngt.service; +import fr.atlantique.imt.inf211.jobmngt.entity.Application; import fr.atlantique.imt.inf211.jobmngt.entity.Candidate; +import fr.atlantique.imt.inf211.jobmngt.entity.JobOffer; + import java.util.List; import java.util.Optional; @@ -14,4 +17,6 @@ public interface CandidateService { void updateCandidate(Candidate candidate); void deleteCandidate(int id); Optional<Candidate> findById(int id); + List<Application> getApplicationByCandidate(int candidateId); + } diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/CandidateServiceImpl.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/CandidateServiceImpl.java index 56b2822..a2c1247 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/CandidateServiceImpl.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/CandidateServiceImpl.java @@ -1,8 +1,14 @@ package fr.atlantique.imt.inf211.jobmngt.service; +import fr.atlantique.imt.inf211.jobmngt.dao.ApplicationDao; import fr.atlantique.imt.inf211.jobmngt.dao.CandidateDao; +import fr.atlantique.imt.inf211.jobmngt.entity.Application; import fr.atlantique.imt.inf211.jobmngt.entity.Candidate; +import fr.atlantique.imt.inf211.jobmngt.entity.JobOffer; +import fr.atlantique.imt.inf211.jobmngt.entity.RoleType; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -10,10 +16,12 @@ import java.util.List; import java.util.Optional; @Service +@RequiredArgsConstructor public class CandidateServiceImpl implements CandidateService { - @Autowired - private CandidateDao candidateDao; + final CandidateDao candidateDao; + final ApplicationDao applicationDao; + final PasswordEncoder passwordEncoder; @Override public List<Candidate> getAllCandidates() { @@ -27,7 +35,7 @@ public class CandidateServiceImpl implements CandidateService { @Override public void createCandidate(Candidate candidate) { - candidateDao.persist(candidate); + candidateDao.save(candidate); } @Override @@ -38,8 +46,9 @@ public class CandidateServiceImpl implements CandidateService { if (existingCandidate.isPresent()) { return false; } - - candidateDao.persist(candidate); + candidate.setPassword(passwordEncoder.encode(candidate.getPassword())); + candidate.setUsertype(RoleType.Candidate.name()); + candidateDao.save(candidate); return true; } @@ -70,5 +79,10 @@ public class CandidateServiceImpl implements CandidateService { return Optional.ofNullable(candidate); } + @Override + public List<Application> getApplicationByCandidate(int candidateId) { + return applicationDao.findByCandidateId(candidateId); + } + } diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/CompanyService.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/CompanyService.java index 7a6bd59..550e70a 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/CompanyService.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/CompanyService.java @@ -9,12 +9,12 @@ import java.util.Optional; public interface CompanyService { void saveCompany(Company company); List<Company> getAllCompanies(); - Optional<Company> findById(Long id); + Optional<Company> findById(int id); Optional<Company> findByMail(String mail); // Ajout pour vérifier les doublons void updateCompany(Company company); // Mise à jour d'une entreprise - void deleteCompany(Long id); // Supprimer une entreprise - int countJobOffersByCompany(Long companyId); - List<JobOffer> getJobOffersByCompany(Long companyId); + void deleteCompany(int id); // Supprimer une entreprise + int countJobOffersByCompany(int companyId); + List<JobOffer> getJobOffersByCompany(int companyId); diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/CompanyServiceImpl.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/CompanyServiceImpl.java index 0d6cae8..380915d 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/CompanyServiceImpl.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/CompanyServiceImpl.java @@ -3,7 +3,8 @@ package fr.atlantique.imt.inf211.jobmngt.service; import java.util.List; import java.util.Optional; -import org.springframework.beans.factory.annotation.Autowired; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import fr.atlantique.imt.inf211.jobmngt.dao.CompanyDao; @@ -12,17 +13,13 @@ import fr.atlantique.imt.inf211.jobmngt.entity.Company; import fr.atlantique.imt.inf211.jobmngt.entity.JobOffer; @Service +@RequiredArgsConstructor public class CompanyServiceImpl implements CompanyService { - private CompanyDao companyDao; - private final JobOfferDao jobOfferDao; + final CompanyDao companyDao; + final JobOfferDao jobOfferDao; - @Autowired - public CompanyServiceImpl(CompanyDao companyDao, JobOfferDao jobOfferDao) { - this.companyDao = companyDao; - this.jobOfferDao = jobOfferDao; - } @Override public void saveCompany(Company company) { @@ -35,7 +32,7 @@ public class CompanyServiceImpl implements CompanyService { } @Override - public Optional<Company> findById(Long id) { + public Optional<Company> findById(int id) { return companyDao.findById(id); // Assure-toi que companyRepository retourne Optional<Company> } @@ -45,7 +42,7 @@ public class CompanyServiceImpl implements CompanyService { } @Override - public void deleteCompany(Long id) { + public void deleteCompany(int id) { companyDao.remove(id); } @@ -56,16 +53,16 @@ public class CompanyServiceImpl implements CompanyService { } @Override - public int countJobOffersByCompany(Long companyId) { + public int countJobOffersByCompany(int companyId) { return jobOfferDao.countByCompanyId(companyId); } @Override - public List<JobOffer> getJobOffersByCompany(Long companyId) { + public List<JobOffer> getJobOffersByCompany(int companyId) { return jobOfferDao.findByCompanyId(companyId); } - public Company getCompanyById(Long id) { + public Company getCompanyById(int id) { return companyDao.findById(id).orElse(null); } diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/JobOfferService.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/JobOfferService.java index 06319c3..0e16e09 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/JobOfferService.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/JobOfferService.java @@ -8,10 +8,10 @@ import fr.atlantique.imt.inf211.jobmngt.entity.JobOffer; public interface JobOfferService { List<JobOffer> getAllJobOffers(); - List<JobOffer> getJobOffersByCompanyId(Long companyId); // 🔹 Nouvelle méthode - Optional<JobOffer> findById(Long id); // ✅ Correction int → Long + List<JobOffer> getJobOffersByCompanyId(int companyId); // 🔹 Nouvelle méthode + Optional<JobOffer> findById(int id); // ✅ Correction int → Long void saveJobOffer(JobOffer jobOffer); // void updateJobOffer(JobOffer jobOffer); - void deleteJobOffer(Long id); // ✅ Correction int → Long + void deleteJobOffer(int id); // ✅ Correction int → Long List<JobOffer> findByCompany(Company company); } diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/JobOfferServiceImpl.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/JobOfferServiceImpl.java index bfe86f4..35e35c2 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/JobOfferServiceImpl.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/JobOfferServiceImpl.java @@ -26,7 +26,7 @@ public class JobOfferServiceImpl implements JobOfferService { return jobOfferDao.findAll(); } - public Optional<JobOffer> findById(Long id) { + public Optional<JobOffer> findById(int id) { Optional<JobOffer> jobOfferOpt = jobOfferDao.findById(id); System.out.println("🔍 Vérification BDD pour l'offre ID " + id + " : " + (jobOfferOpt.isPresent() ? "Trouvée ✅" : "Non trouvée ❌")); return jobOfferOpt; @@ -36,8 +36,7 @@ public class JobOfferServiceImpl implements JobOfferService { @Override public void saveJobOffer(JobOffer jobOffer) { if (jobOffer.getPublicationDate() == null) { - jobOffer.setPublicationDate(new Date()); - jobOffer.setPublicationDate(new Date()); + jobOffer.setPublicationDate(jobOffer.getPublicationDate()); } jobOfferDao.save(jobOffer); System.out.println(" Enregistrement réussi !"); @@ -52,12 +51,12 @@ public class JobOfferServiceImpl implements JobOfferService { // } @Override - public void deleteJobOffer(Long id) { // ✅ Correction int → Long + public void deleteJobOffer(int id) { // ✅ Correction int → Long jobOfferDao.deleteById(id); } @Override - public List<JobOffer> getJobOffersByCompanyId(Long companyId) { + public List<JobOffer> getJobOffersByCompanyId(int companyId) { return jobOfferDao.findByCompanyId(companyId); // 🔹 Récupère les offres par entreprise } diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/MatchingIndexService.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/MatchingIndexService.java new file mode 100644 index 0000000..5a5bffa --- /dev/null +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/MatchingIndexService.java @@ -0,0 +1,134 @@ +package fr.atlantique.imt.inf211.jobmngt.service; + +import fr.atlantique.imt.inf211.jobmngt.dao.AppUserDao; +import fr.atlantique.imt.inf211.jobmngt.dao.ApplicationDao; +import fr.atlantique.imt.inf211.jobmngt.dao.JobOfferDao; +import fr.atlantique.imt.inf211.jobmngt.dao.SectorDao; +import fr.atlantique.imt.inf211.jobmngt.entity.Application; +import fr.atlantique.imt.inf211.jobmngt.entity.JobOffer; +import fr.atlantique.imt.inf211.jobmngt.entity.QualificationLevel; +import fr.atlantique.imt.inf211.jobmngt.entity.Sector; +import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class MatchingIndexService { + final JobOfferDao jobOfferDao; + final ApplicationDao applicationRepository; + final SectorDao sectorDao; + final QualificationLevelService qualificationLevelService; + private static final Logger logger = LoggerFactory.getLogger(MatchingIndexService.class.getName()); + + @Transactional + public void indexJobOffer(JobOffer jobOffer) { + // Mise à jour des index pour une offre existante + if (jobOffer.getId() != 0) { + JobOffer existing = jobOfferDao.findById(jobOffer.getId()).orElse(null); + if (existing != null) { + updateIndex(existing, false); // Supprime l'ancien index + } + } + updateIndex(jobOffer, true); // Crée le nouvel index + } + + @Transactional + public void indexApplication(Application application) { + // Logique similaire pour les candidatures + if (application.getId() != 0) { + Application existing = applicationRepository.findById(application.getId()).orElse(null); + if (existing != null) { + updateIndex(existing, false); + } + } + updateIndex(application, true); + } + + @Transactional + public void updateIndex(JobOffer jobOffer, boolean add) { + if (add) { + // Sauvegarde des associations existantes (automatique avec CascadeType.MERGE/PERSIST) + // Alternative explicite : + Collection<Sector> sectors = jobOffer.getSectors(); + if (sectors != null) { + sectors.forEach(sector -> { + sector.getJobOffers().add(jobOffer); + sectorDao.persist(sector); + }); + } + } else { + // Nettoyage des anciennes associations + Collection<Sector> oldSectors = jobOffer.getSectors(); + if (oldSectors != null) { + oldSectors.forEach(sector -> { + sector.getJobOffers().remove(jobOffer); + sectorDao.persist(sector); + }); + } + } + } + + @Transactional + public void updateIndex(Application application, boolean add) { + Collection<Sector> sectors = application.getSectors(); + if (sectors == null || sectors.isEmpty()) return; + + for (Sector sector : sectors) { + // 1. Initialiser la collection si nécessaire + if (sector.getApplications() == null) { + sector.setApplications(new HashSet<>()); + } + + // 2. Mettre à jour la relation en mémoire + if (add) { + sector.getApplications().add(application); + } else { + sector.getApplications().remove(application); + } + + // 3. Sauvegarder le secteur via votre DAO + sectorDao.update(sector); // Méthode existante dans votre SectorDao + } + } + @Transactional(readOnly = true) + public List<JobOffer> findMatchingJobOffers(Application application) { + logger.info("Matching application ID {} with sectors: {}", + application.getId(), + application.getSectors().stream().map(Sector::getLabel).collect(Collectors.toList()) + ); + QualificationLevel appQl = application.getQualificationlevel(); + Collection<Sector> appSectors = application.getSectors(); + + return jobOfferDao.findAll().stream() + .filter(offer -> offer.getQualificationLevel().equals(appQl)) + .filter(offer -> !Collections.disjoint(offer.getSectors(), appSectors)) + .sorted(Comparator.comparingInt(offer -> + -countCommonSectors(offer.getSectors(), appSectors))) // Tri par nombre de secteurs communs + .collect(Collectors.toList()); + } + + @Transactional(readOnly = true) + public Collection<Application> findMatchingApplications(JobOffer jobOffer) { + QualificationLevel offerQl = jobOffer.getQualificationLevel(); + Collection<Sector> offerSectors = jobOffer.getSectors(); + + return applicationRepository.findAll().stream() + .filter(app -> app.getQualificationlevel().equals(offerQl)) + .filter(app -> !Collections.disjoint(app.getSectors(), offerSectors)) + .sorted(Comparator.comparingInt(app -> + -countCommonSectors(app.getSectors(), offerSectors))) + .collect(Collectors.toList()); + } + + private int countCommonSectors(Collection<Sector> set1, Collection<Sector> set2) { + return (int) set1.stream() + .filter(set2::contains) + .count(); + } +} diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/SectorService.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/SectorService.java index 185a275..5bbc1b9 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/SectorService.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/SectorService.java @@ -1,20 +1,21 @@ package fr.atlantique.imt.inf211.jobmngt.service; +import java.util.Collection; import java.util.List; import java.util.Optional; +import java.util.Set; import fr.atlantique.imt.inf211.jobmngt.entity.Sector; public interface SectorService { - List<Sector> listOfSectors(); // ✅ Ajout de cette méthode + Collection<Sector> listOfSectors(); // ✅ Ajout de cette méthode Optional<Sector> findById(int id); // ✅ Changer int en Long void saveSector(Sector sector); void updateSector(Sector sector); void deleteSector(int id); - List<Sector> getAllSectors(); - List<Sector> getSectorsByIds(List<Integer> sectorIds); - Optional<Sector> findById(long id); + Collection<Sector> getAllSectors(); + Collection<Sector> getSectorsByIds(List<Integer> sectorIds); - public long countSectors(); + public Integer countSectors(); } diff --git a/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/SectorServiceImpl.java b/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/SectorServiceImpl.java index b4d86f0..c42834e 100644 --- a/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/SectorServiceImpl.java +++ b/src/main/java/fr/atlantique/imt/inf211/jobmngt/service/SectorServiceImpl.java @@ -1,7 +1,9 @@ package fr.atlantique.imt.inf211.jobmngt.service; +import java.util.Collection; import java.util.List; import java.util.Optional; +import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -16,7 +18,7 @@ public class SectorServiceImpl implements SectorService { private SectorDao sectorDao; @Override - public List<Sector> listOfSectors() { + public Collection<Sector> listOfSectors() { return sectorDao.findAll(); // ✅ Appel de la nouvelle méthode sans arguments } @@ -26,25 +28,20 @@ public class SectorServiceImpl implements SectorService { } @Override - public List<Sector> getAllSectors() { + public Collection<Sector> getAllSectors() { return sectorDao.findAll(null, null); } - public List<Sector> getSectorsByIds(List<Integer> sectorIds) { + public Collection<Sector> getSectorsByIds(List<Integer> sectorIds) { return sectorDao.findAllByIds(sectorIds); } @Override - public long countSectors() { + public Integer countSectors() { return sectorDao.count(); } - @Override - public Optional<Sector> findById(long id) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'findById'"); - } public void saveSector(Sector sector) { sectorDao.save(sector); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a5b3aed..c970441 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -18,7 +18,7 @@ spring.jpa.show-sql=true logging.level.root=info logging.level.org.springframework.web=debug spring.jackson.serialization.indent_output=true - +spring.thymeleaf.expose-authentication-attributes=true # Session timeout in seconds server.servlet.session.timeout=10m @@ -29,4 +29,11 @@ jobmngt.admin=007 spring.thymeleaf.cache=false -spring.jackson.serialization.FAIL_ON_EMPTY_BEANS=false \ No newline at end of file +spring.jackson.serialization.FAIL_ON_EMPTY_BEANS=false + +temp.upload.dir=./temp_uploads + +# Taille max des fichiers +spring.servlet.multipart.max-file-size=5MB +spring.servlet.multipart.max-request-size=5MB +spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true diff --git a/src/main/resources/static/css/gyj_imt.css b/src/main/resources/static/css/gyj_imt.css index fae8405..4cc3cb8 100644 --- a/src/main/resources/static/css/gyj_imt.css +++ b/src/main/resources/static/css/gyj_imt.css @@ -7,6 +7,9 @@ #header_div{ color: white; background-color: rgb(179, 199, 71); + padding: 10px; + + } #tiny_text{ @@ -20,7 +23,7 @@ #content_div{ background-color: white; - + } #central_div{ padding: 10px; @@ -32,6 +35,15 @@ color: white; background-color: rgb(0, 32, 65); } +.favorite_color { + color: rgb(0, 184, 222); +} +.favorite_back { + background-color: rgb(0, 184, 222); +} +.favorite_outline { + border-color: rgb(0, 184, 222); +} .logo{ width:30px; @@ -148,4 +160,82 @@ .card-black h2 { color: white !important; } +/* Ajoutez ces styles pour améliorer l'interface */ +.navbar { + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + + +.alert-info { + background-color: #f8f9fa; + border-left: 4px solid #072262; + color: #072262; +} + +.nav-link { + font-weight: 500; +} + +.btn-outline-light:hover { + color: #072262 !important; +} +/* Dashboard */ +.card { + border-radius: 10px; + box-shadow: 0 2px 5px rgba(0,0,0,0.1); + transition: transform 0.3s; +} + +.card:hover { + transform: translateY(-3px); +} + +.card-header { + border-radius: 10px 10px 0 0 !important; +} + +/* Profil */ +dl.row dt { + font-weight: normal; + color: #6c757d; +} + +.list-group-item { + border-left: 3px solid transparent; + transition: all 0.3s; +} + +.list-group-item:hover { + border-left-color: #072262; + background-color: #f8f9fa; +} +/* Style cohérent pour l'interface anglaise */ +.navbar-brand { + font-weight: 600; +} + + +.btn-outline-danger { + border-color: #dc3545; + color: #dc3545; +} +/* Style pour la bannière de bienvenue */ +.welcome-banner { + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + transition: all 0.3s ease; +} + +.welcome-banner:hover { + transform: translateY(-2px); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); +} + +.welcome-banner i { + font-size: 1.5rem; +} +/* Style pour le nom utilisateur */ +.text-white { + font-size: 1rem; + vertical-align: middle; +} diff --git a/src/main/resources/templates/application/application-confirmation.html b/src/main/resources/templates/application/application-confirmation.html index 49c8ab4..aa8fd92 100644 --- a/src/main/resources/templates/application/application-confirmation.html +++ b/src/main/resources/templates/application/application-confirmation.html @@ -1,36 +1,130 @@ -<!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> -<head> - <meta charset="UTF-8"> - <title>Confirmation de Candidature</title> - <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> -</head> -<body> - <div class="container"> - <div class="alert alert-success mt-5 text-center"> - <h2> Votre candidature a été soumise avec succès !</h2> - </div> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Application Confirmation</title> +<section> + <head> + <meta charset="UTF-8"> + <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> + <style> + .confirmation-card { + border-radius: 12px; + box-shadow: 0 6px 15px rgba(0, 0, 0, 0.08); + border: none; + overflow: hidden; + } + .confirmation-header { + background:#00B8DEFF; + color: white; + padding: 1.8rem; + text-align: center; + } + .confirmation-icon { + font-size: 2.5rem; + margin-bottom: 1rem; + } + .detail-card { + border-left: 4px solid #00B8DEFF; + border-radius: 8px; + margin-bottom: 1.5rem; + } + .detail-item { + padding: 1rem 1.5rem; + border-bottom: 1px solid #f0f0f0; + } + .detail-item:last-child { + border-bottom: none; + } + .sector-badge { + background-color: #e8f0fe; + color: #00B8DEFF; + padding: 6px 12px; + border-radius: 18px; + margin-right: 8px; + margin-bottom: 8px; + display: inline-block; + font-size: 0.85rem; + } + .cv-link i { + margin-right: 8px; + } + .timestamp { + color: #5f6368; + font-size: 0.9rem; + } + </style> + </head> + <body> + <div class="container py-5"> + <div class="row justify-content-center"> + <div class="col-lg-8"> + <!-- Confirmation Card --> + <div class="card confirmation-card"> + <div class="confirmation-header"> + <div class="confirmation-icon"> + <i class="fas fa-check-circle"></i> + </div> + <h2>Application Submitted Successfully</h2> + <p class="mb-0">Thank you for your application</p> + </div> - <h3 class="mt-4">📄 Détails de la candidature :</h3> + <div class="card-body p-4"> + <!-- Application Details --> + <div class="card detail-card mb-4"> + <div class="card-body p-0"> + <div class="detail-item"> + <h5 class="text-muted mb-3">Application Details</h5> + <dl class="row"> + <dt class="col-sm-4">Application Number</dt> + <dd class="col-sm-8 font-weight-bold" th:text="${appId}"></dd> - <div th:if="${application != null}"> - <table class="table table-bordered"> - <tr><th>ID</th><td th:text="${application.id != null ? application.id : 'Non spécifié'}"></td></tr> - <tr><th> CV</th><td th:text="${application.cv != null ? application.cv : 'Non spécifié'}"></td></tr> - <tr><th> Niveau de qualification</th> - <td th:text="${application.qualificationlevel != null ? application.qualificationlevel.label : 'Non spécifié'}"></td> - </tr> - <tr><th> Secteurs d'activité</th> - <td th:each="sector : ${application.sectors}" th:text="${sector.label}"></td> - </tr> - <tr><th> Date de dépôt</th> - <td th:text="${application.appdate != null ? application.appdate : 'Non spécifié'}"></td> - </tr> - - </table> - </div> + <dt class="col-sm-4">Submission Date</dt> + <dd class="col-sm-8"> + <span th:text="${#temporals.format(appDate, 'dd MMMM yyyy - HH:mm')}" class="timestamp"></span> + </dd> + </dl> + </div> + </div> + </div> + + <!-- Candidate Information --> + <div class="card detail-card"> + <div class="card-body p-0"> + <div class="detail-item"> + <h5 class="text-muted mb-3">Your Information</h5> + <dl class="row"> + <dt class="col-sm-4">CV Document</dt> + <dd class="col-sm-8"> + <span th:text="${appCv} ?: 'Not specified'" + class="badge favorite_back text-white"></span> + </dd> + + <dt class="col-sm-4">Qualification Level</dt> + <dd class="col-sm-8"> + <span th:text="${qualification?.label} ?: 'Not specified'" + class="badge favorite_back text-white"></span> + </dd> - <a href="/" class="btn btn-primary mt-3"> Retour à l'accueil</a> + <dt class="col-sm-4">Activity Sectors</dt> + <dd class="col-sm-8"> + <div th:each="sector : ${sectors}"> + <span class="sector-badge" th:text="${sector.label}"></span> + </div> + <div th:if="${#lists.isEmpty(sectors)}" class="text-muted"> + No sectors specified + </div> + </dd> + </dl> + </div> + </div> + </div> + + </div> + </div> + </div> + </div> </div> -</body> -</html> + + <!-- Font Awesome for icons --> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> + </body> +</section> +</html> \ No newline at end of file diff --git a/src/main/resources/templates/application/application-details.html b/src/main/resources/templates/application/application-details.html index 4a53d5b..1f06033 100644 --- a/src/main/resources/templates/application/application-details.html +++ b/src/main/resources/templates/application/application-details.html @@ -1,49 +1,113 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> <head> - <meta charset="UTF-8"> - <title>Détails de la Candidature</title> - <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> + <title>Applications</title> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"> + <style> + :root { + --primary: #072262; + --accent: #1ABC9C; + --text-primary: #333333; + --text-secondary: #666666; + --bg-light: #F5F5F5; + --border-light: #E0E0E0; + } + + .favorite_color { + color: var(--primary); + } + + .favorite_back { + background-color: var(--accent); + color: white; + border: none; + } + + .favorite_outline { + border-color: var(--accent); + color: var(--accent); + } + + .favorite_outline:hover { + background-color: var(--accent); + color: white; + } + </style> </head> <body> - <div class="container mt-5"> - <h2 class="mb-4"> Détails de la Candidature</h2> +<section> + <header class="mb-4"> + <h2 class="fw-bold border-bottom pb-2">Application Details</h2> + </header> - <div th:if="${error}" class="alert alert-warning"> - <p th:text="${error}"></p> - </div> + <!-- Alert Messages --> + <div th:if="${error}" class="alert alert-warning alert-dismissible fade show" role="alert"> + <i class="bi bi-exclamation-triangle-fill me-2"></i> + <span th:text="${error}"></span> + <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> + </div> + + <div class="card shadow-sm mb-4"> + <div class="card-body"> + <div class="row g-3"> + <!-- Left Column --> + <div class="col-md-6"> + <h4 class="favorite_color fw-bold mb-3"> + <i class="bi bi-person-badge"></i> Candidate Information + </h4> - <div class="card"> - <div class="card-body"> - <h5 class="card-title"> - Nom: - <span th:if="${application.candidate != null}" th:text="${application.candidate.firstname + ' ' + application.candidate.lastname}"></span> - <span th:unless="${application.candidate != null}">Non disponible</span> - </h5> - - <p class="card-text"> - <span th:if="${application.cv != null}" th:text="'CV: ' + ${application.cv}"></span> - <span th:unless="${application.cv != null}">Non disponible</span> - </p> - - <p class="card-text"> - <span th:if="${application.qualificationlevel != null}" th:text="'Niveau de Qualification: ' + ${application.qualificationlevel.label}"></span> - <span th:unless="${application.qualificationlevel != null}">Non disponible</span> - </p> - - <p class="card-text"> - <span th:each="sector : ${application.sectors}" th:text="'Secteur: ' + ${sector.label}"></span> - <span th:if="${#lists.isEmpty(application.sectors)}">Secteurs non disponibles</span> - </p> - - <p class="card-text"> - <span th:if="${application.appdate != null}" th:text="'Date de dépôt: ' + ${application.appdate}"></span> - <span th:unless="${application.appdate != null}">Non disponible</span> - </p> + <div class="mb-3"> + <label class="form-label fw-semibold">Full Name</label> + <div class="form-control-plaintext bg-light p-2 rounded"> + <span th:text="${candidate.firstname + ' ' + candidate.lastname}"></span> + </div> + </div> + + <div class="mb-3"> + <label class="form-label fw-semibold">CV</label> + <div class="form-control-plaintext bg-light p-2 rounded"> + <span th:text="${cv}"></span> + </div> + </div> + <div class="mb-3"> + <label class="form-label fw-semibold">Application Date</label> + <div class="form-control-plaintext bg-light p-2 rounded"> + <span th:text="${#temporals.format(appdate, 'MMMM dd, yyyy')}"></span> + </div> + </div> + </div> + + <!-- Right Column --> + <div class="col-md-6"> + <h4 class="favorite_color fw-bold mb-3"> + <i class="bi bi-award"></i> Qualifications + </h4> + + <div class="mb-3"> + <label class="form-label fw-semibold">Qualification Level</label> + <div class="form-control-plaintext bg-light p-2 rounded"> + <span th:text="${qualification.label}"></span> + </div> + </div> + + <div class="mb-3"> + <label class="form-label fw-semibold">Sectors</label> + <div class="form-control-plaintext bg-light p-2 rounded"> + <span th:each="sector : ${sectors}" class="badge favorite_back me-1 mb-1" + th:text="${sector.label}"></span> + </div> + </div> + </div> </div> </div> - - <a th:href="@{/applications/list}" class="btn btn-primary mt-3">Retour à la liste des candidatures</a> </div> + + <footer class="d-flex justify-content-between mt-4"> + <a th:href="@{/applications/list}" class="btn btn-outline-secondary"> + <i class="bi bi-arrow-left"></i> Back to Applications List + </a> + </footer> +</section> </body> </html> \ No newline at end of file diff --git a/src/main/resources/templates/application/application-edit.html b/src/main/resources/templates/application/application-edit.html index f68c9ec..6a3bb68 100644 --- a/src/main/resources/templates/application/application-edit.html +++ b/src/main/resources/templates/application/application-edit.html @@ -1,17 +1,240 @@ -<form th:action="@{/applications/update}" method="post"> - <input type="hidden" name="id" th:value="${application.id}"> - <label>CV :</label> - <input type="text" name="cv" th:value="${application.cv}" required> - - <label>Niveau de qualification :</label> - <select name="qualificationLevel"> - <option th:each="q : ${qualifications}" th:value="${q.id}" th:text="${q.label}"></option> - </select> - - <label>Secteurs :</label> - <select name="sectors" multiple> - <option th:each="s : ${sectors}" th:value="${s.id}" th:text="${s.label}"></option> - </select> - - <button type="submit">Modifier</button> -</form> + <!DOCTYPE html> + <html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> + <title>Edit Job Application</title> + <section> + <head> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"> + <style> + .form-container { + max-width: 800px; + margin: 0 auto; + background-color: #fff; + border-radius: 8px; + padding: 2rem; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + } + .sector-item { + display: inline-block; + margin: 5px; + cursor: pointer; + padding: 5px 10px; + border-radius: 4px; + background: #f8f9fa; + border: 1px solid #dee2e6; + } + .sector-item.selected { + background: #00B8DEFF; + color: white; + border-color: #00B8DEFF; + } + #selectedSectors { + min-height: 40px; + border: 1px solid #ced4da; + border-radius: 4px; + padding: 8px; + margin-top: 5px; + } + .file-upload { + border: 2px dashed #dee2e6; + border-radius: 8px; + padding: 20px; + text-align: center; + cursor: pointer; + transition: all 0.3s; + } + .file-upload:hover { + border-color: #00B8DEFF; + background-color: #f8f9fa; + } + .file-upload i { + font-size: 2rem; + color: #6c757d; + } + .file-name { + margin-top: 10px; + font-weight: bold; + color: #00B8DEFF; + } + .btn-primary { + background-color: rgb(0, 184, 222); + border-color: rgb(0, 184, 222); + } + </style> + </head> + <body class="bg-light"> + + <div class="container py-4"> + <div class="form-container"> + <h2 class="text-center mb-4"> + Edit Job Application + </h2> + <form th:action="@{/applications/update}" method="post" class="needs-validation" novalidate> + <input type="hidden" name="id" th:value="${id}" /> + <!-- Improved CV Field --> + <div class="mb-3"> + <label class="form-label">Resume*</label> + <div class="file-upload" onclick="document.getElementById('cvFile').click()"> + <i class="bi bi-upload"></i> + <div id="cvText">Click to upload your resume</div> + <div id="fileName" class="file-name d-none"></div> + <input type="file" id="cvFile" class="d-none bold" accept=".pdf,.doc,.docx"> + <input type="hidden" id="cv" name="cv" th:text="${cv}" required> + </div> + <small class="text-muted">Accepted formats: PDF, DOC, DOCX</small> + <div class="invalid-feedback">Please upload your resume</div> + </div> + + <div class="col-md-6"> + <label class="form-label">Publication Date*</label> + <input type="date" class="form-control" name="publicationDate" required + th:value="${appDate != null} ? ${#temporals.format(appDate, 'yyyy-MM-dd')} : ${#temporals.format(#temporals.createToday(), 'yyyy-MM-dd')}"/> + </div> + + <!-- Qualification Level --> + <div class="col-md-6"> + <label class="form-label">Qualification Level*</label> + <select class="form-select" name="qualificationLevel" required> + <option value="">-- Select level --</option> + <option th:each="level : ${qualifications}" + th:value="${level.id}" + th:text="${level.label}" + th:selected="${qualApp?.id == level.id}"> + </option> + </select> + </div> + + <!-- Industry Sectors - Version corrigée --> + <div class="mb-3"> + <label class="form-label">Industry Sectors*</label> + + <!-- Liste complète des secteurs --> + <div class="mb-2"> + <div th:each="sector : ${sectors}" + class="sector-item" + th:data-id="${sector.id}" + th:classappend="${#lists.contains(sectorApp.![id], sector.id)} ? 'selected' : ''" + th:text="${sector.label}" + onclick="toggleSector(this)"></div> + </div> + + <!-- Champ affichant les secteurs sélectionnés --> + <div id="selectedSectorsDisplay" class="mb-2"> + <span th:if="${sectorApp.empty}">No sectors selected</span> + <th:block th:each="sector : ${sectorApp}"> + <div th:data-id="${sector.id}"> + <span th:text="${sector.label}"></span> + </div> + </th:block> + </div> + + <input type="hidden" id="sectorIds" name="sectorIds" + th:value="${#strings.listJoin(sectorApp.![id], ',')}" required> + <div class="invalid-feedback">Please select at least one sector</div> + </div> + + <!-- Buttons --> + <div class="d-flex justify-content-between mt-4"> + <a th:href="@{/applications/list}" class="btn btn-outline-secondary"> + <i class="bi bi-arrow-left me-1"></i> Cancel + </a> + <button type="submit" class="btn btn-primary"> + <i class="bi bi-save me-1"></i> Save Changes + </button> + </div> + </form> + </div> + </div> + + <script> + // Initialize with already selected sectors from the application object + const selectedSectors = new Set( + document.getElementById('sectorIds').value.split(',').filter(Boolean) + ); + + // Mark selected sectors on page load + document.addEventListener('DOMContentLoaded', function() { + updateSelectedDisplay(); + + // Mark initially selected sectors in the list + selectedSectors.forEach(sectorId => { + const element = document.querySelector(`.sector-item[data-id="${sectorId}"]`); + if (element) { + element.classList.add('selected'); + } + }); + }); + + function toggleSector(element) { + const sectorId = element.getAttribute('data-id'); + const sectorLabel = element.textContent; + + if (selectedSectors.has(sectorId)) { + selectedSectors.delete(sectorId); + element.classList.remove('selected'); + } else { + selectedSectors.add(sectorId); + element.classList.add('selected'); + } + + updateSelectedDisplay(); + } + + function updateSelectedDisplay() { + const displayDiv = document.getElementById('selectedSectorsDisplay'); + const hiddenInput = document.getElementById('sectorIds'); + + // Clear the display + displayDiv.innerHTML = ''; + + if (selectedSectors.size === 0) { + displayDiv.innerHTML = '<span>No sectors selected</span>'; + } else { + // Add selected sectors as tags + selectedSectors.forEach(sectorId => { + const sectorElement = document.querySelector(`.sector-item[data-id="${sectorId}"]`); + if (sectorElement) { + const tag = document.createElement('div'); + tag.setAttribute('data-id', sectorId); + tag.innerHTML = `<span>${sectorElement.textContent}</span>`; + displayDiv.appendChild(tag); + } + }); + } + + // Update hidden input with selected sector IDs + hiddenInput.value = Array.from(selectedSectors).join(','); + } + + + + // File upload handling + document.getElementById('cvFile').addEventListener('change', function(e) { + const file = e.target.files[0]; + if (file) { + document.getElementById('cvText').classList.add('d-none'); + document.getElementById('fileName').textContent = file.name; + document.getElementById('fileName').classList.remove('d-none'); + document.getElementById('cv').value = file.name; + } + }); + + // Form validation + (function() { + 'use strict'; + const forms = document.querySelectorAll('.needs-validation'); + + Array.from(forms).forEach(form => { + form.addEventListener('submit', event => { + if (!form.checkValidity()) { + event.preventDefault(); + event.stopPropagation(); + } + form.classList.add('was-validated'); + }, false); + }); + })(); + </script> + </body> + </section> + </html> \ No newline at end of file diff --git a/src/main/resources/templates/application/application-list.html b/src/main/resources/templates/application/application-list.html index 2200f31..2bdac21 100644 --- a/src/main/resources/templates/application/application-list.html +++ b/src/main/resources/templates/application/application-list.html @@ -1,75 +1,133 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> -<head> - <meta charset="UTF-8"> - <title>Liste des Candidatures</title> - <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> -</head> -<body> - <div class="container mt-5"> - <h2 class="mb-4">Liste des Candidatures</h2> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Applications</title> +<section> + <head> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"> + <style> + .application-card { + transition: all 0.3s ease; + border-left: 4px solid transparent; + } + .application-card:hover { + transform: translateY(-2px); + border-left-color: #00b8de; + background-color: #f8f9fa; + } + .table-container { + border-radius: 8px; + overflow: hidden; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); + } + .sector-badge { + margin-right: 4px; + margin-bottom: 4px; + } + .empty-state-icon { + font-size: 3rem; + opacity: 0.5; + } + </style> + </head> + <body class="bg-light"> - <div th:if="${error}" class="alert alert-warning"> - <p th:text="${error}"></p> + <div class="container py-4"> + <!-- Header Section --> + <div class="d-flex justify-content-between align-items-center mb-4"> + <h2 class="fw-bold text-black mb-0"> + <i class="bi bi-file-earmark-text me-2"></i> Job Applications + </h2> </div> - <table class="table table-bordered table-striped" th:if="${applicationsList}"> - <thead> - <tr> - <th>ID</th> - <th>Nom</th> - <th>Prénom</th> - <th>CV</th> - <th>Niveau de Qualification</th> - <th>Secteurs d'activité</th> - <th>Date de dépôt</th> - <th>Détails</th> - <th>Modifier</th> - <th>Supprimer</th> - </tr> - </thead> - <tbody> - <tr th:each="app : ${applicationsList}"> - <td th:text="${app.id}"></td> - <td th:text="${app.candidate.firstname}"></td> - <td th:text="${app.candidate.lastname}"></td> - <td><a th:href="@{${app.cv}}" th:text="${app.cv}"></a></td> - <td th:text="${app.qualificationlevel.label}"></td> - <td> - <ul> - <li th:each="sector : ${app.sectors}" th:text="${sector.label}"></li> - </ul> - </td> - <td th:text="${app.appdate}"></td> - <td> - <a th:href="@{/applications/details/{id}(id=${app.id})}" class="btn btn-info">Détails</a> - </td> - <!-- Vérification que l'utilisateur est connecté et est le propriétaire --> - <td th:if="${session.uid != null && session.uid == app.candidate.id}"> - <a th:href="@{/applications/edit/{id}(id=${app.id})}" class="btn btn-warning">Modifier</a> - </td> - <td th:if="${session.uid != null && session.uid == app.candidate.id}"> - <a th:href="@{/applications/delete/{id}(id=${app.id})}" class="btn btn-danger" - onclick="return confirm('Êtes-vous sûr de vouloir supprimer cette candidature ?');"> - Supprimer - </a> - </td> - <!-- Affichage d'un message si l'utilisateur n'est pas connecté --> - <td th:if="${session.uid == null}" colspan="2"> - <a th:href="@{/login}" class="btn btn-secondary">Se connecter</a> - </td> - </tr> - </tbody> - </table> + <!-- Applications Table --> + <div class="card shadow-sm table-container"> + <div class="card-body p-0"> + <div class="table-responsive"> + <table class="table table-hover align-middle mb-0" th:if="${not #lists.isEmpty(applicationsList)}"> + <thead class="table-light"> + <tr> + <th class="ps-4">First Name</th> + <th>Last Name</th> + <th>Qualification</th> + <th>Sectors</th> + <th>Applied Date</th> + <th class="text-end pe-4">Actions</th> + </tr> + </thead> + <tbody> + <tr th:each="app : ${applicationsList}" class="application-card"> + <td class="ps-4 fw-semibold" th:text="${app.candidate.firstname}"></td> + <td th:text="${app.candidate.lastname}"></td> + <td th:text="${app.qualificationlevel?.label} ?: 'N/A'"></td> + <td> + <div class="d-flex flex-wrap"> + <span th:each="sector : ${app.sectors}" + class="badge favorite_back sector-badge" + th:text="${sector.label}"></span> + </div> + </td> + <td th:text="${#temporals.format(app.appdate, 'yyyy-MM-dd')}"></td> + <td class="text-end pe-4"> + <div class="d-flex gap-2 justify-content-end"> + <a th:href="@{'/applications/details/' + ${app.id}}" + class="btn btn-sm favorite_outline"> + <i class="bi bi-eye"></i> View + </a> + <th:block th:if="${session != null and session.userType == 'Candidate' and session.loggedInUser.id == app.candidate.id}"> + <a th:href="@{'/applications/edit/' + ${app.id}}" + class="btn btn-sm btn-outline-warning"> + <i class="bi bi-pencil"></i> Edit + </a> + <a th:href="@{'/applications/delete/' + ${app.id}}" + class="btn btn-sm btn-outline-danger" + onclick="return confirm('Are you sure you want to delete this application?');"> + <i class="bi bi-trash"></i> Delete + </a> + </th:block> + </div> + </td> + </tr> + </tbody> + </table> - <a th:href="@{/}" class="btn btn-primary mt-3">Retour à l'accueil</a> + <!-- Empty State --> + <div th:if="${#lists.isEmpty(applicationsList)}" class="text-center py-5"> + <i class="bi bi-file-earmark-x empty-state-icon text-muted"></i> + <h4 class="mt-3 text-muted">No applications found</h4> + </div> + </div> + </div> + </div> </div> -</body> + + <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> + </body> +</section> </html> +<!-- +</td> +<td> + <a th:href="@{/applications/details/{id}(id=${app.id})}" class="btn btn-info">Détails</a> +</td> + +<td th:if="${session.uid != null && session.uid == app.candidate.id}"> + <a th:href="@{/applications/edit/{id}(id=${app.id})}" class="btn btn-warning">Modifier</a> +</td> +<td th:if="${session.uid != null && session.uid == app.candidate.id}"> + <a th:href="@{/applications/delete/{id}(id=${app.id})}" class="btn btn-danger" + onclick="return confirm('Êtes-vous sûr de vouloir supprimer cette candidature ?');"> + Supprimer + </a> +</td> +<td th:if="${session.uid == null}" colspan="2"> + <a th:href="@{/login}" class="btn btn-secondary">Se connecter</a> +</td> +!--> diff --git a/src/main/resources/templates/application/application-update-form.html b/src/main/resources/templates/application/application-update-form.html index 26266c1..3eefb8c 100644 --- a/src/main/resources/templates/application/application-update-form.html +++ b/src/main/resources/templates/application/application-update-form.html @@ -1,6 +1,8 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> -<head> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Applications</title> +<section> + <head> <meta charset="UTF-8"> <title>Mettre à jour la Candidature</title> <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> @@ -12,7 +14,7 @@ <form th:action="@{/applications/update/{id}(id=${application.id})}" method="post"> <div class="form-group"> <label for="cv">CV</label> - <input type="text" id="cv" name="cv" class="form-control" th:value="${application.cv}" required> + <input type="text" id="cv" name="cvPath" class="form-control" th:value="${application.cv}" required> </div> <div class="form-group"> @@ -37,10 +39,11 @@ </select> </div> - <button type="submit" class="btn btn-primary mt-3">Mettre à jour</button> + <button type="submit" class="btn favorite_back mt-3">Mettre à jour</button> </form> <a th:href="@{/applications/list}" class="btn btn-secondary mt-3">Retour à la liste des candidatures</a> </div> </body> +</section> </html> diff --git a/src/main/resources/templates/application/apply.html b/src/main/resources/templates/application/apply.html index 4615526..66f297c 100644 --- a/src/main/resources/templates/application/apply.html +++ b/src/main/resources/templates/application/apply.html @@ -1,74 +1,192 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> -<head> - <meta charset="UTF-8"> - <title>Postuler à une offre</title> - <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> - <style> - body { - background-color: #f8f9fa; /* Couleur de fond */ - } - .container { - max-width: 600px; - margin-top: 50px; - background: #ffffff; - padding: 20px; - border-radius: 10px; - box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1); - } - h2 { - text-align: center; - color: #007bff; /* Bleu Bootstrap */ - } - label { - font-weight: bold; - color: #343a40; - } - .form-control { - margin-bottom: 10px; - } - .btn-submit { - background-color: #007bff; - color: white; - width: 100%; - } - .btn-submit:hover { - background-color: #0056b3; - } - </style> -</head> -<body> - <div class="container"> - <h2> Postuler à une offre</h2> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Job Application</title> +<section> + <head> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"> + <style> + .form-container { + max-width: 800px; + margin: 0 auto; + background-color: #fff; + border-radius: 8px; + padding: 2rem; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + } + .sector-item { + display: inline-block; + margin: 5px; + cursor: pointer; + padding: 5px 10px; + border-radius: 4px; + background: #f8f9fa; + border: 1px solid #dee2e6; + } + .sector-item.selected { + background: #0d6efd; + color: white; + border-color: #0d6efd; + } + #selectedSectors { + min-height: 40px; + border: 1px solid #ced4da; + border-radius: 4px; + padding: 8px; + margin-top: 5px; + } + .file-upload { + border: 2px dashed #dee2e6; + border-radius: 8px; + padding: 20px; + text-align: center; + cursor: pointer; + transition: all 0.3s; + } + .file-upload:hover { + border-color: #0d6efd; + background-color: #f8f9fa; + } + .file-upload i { + font-size: 2rem; + color: #6c757d; + } + .file-name { + margin-top: 10px; + font-weight: bold; + color: #0d6efd; + } + </style> + </head> + <body class="bg-light"> + + <div class="container py-4"> + <div class="form-container"> + <h2 class="text-center mb-4"> + <i class="bi bi-person-plus me-2"></i> + Apply for Job + </h2> - <form th:action="@{/applications/apply}" method="post"> - <!-- Champ CV --> - <div class="form-group"> - <label for="cv">CV :</label> - <input type="text" id="cv" name="cv" class="form-control" placeholder="Lien vers votre CV" required> - </div> + <form th:action="@{/applications/apply}" method="post" class="needs-validation" novalidate> + <!-- Improved CV Field --> + <div class="mb-3"> + <label class="form-label">Resume*</label> + <div class="file-upload" onclick="document.getElementById('cvFile').click()"> + <i class="bi bi-upload"></i> + <div id="cvText">Click to upload your resume</div> + <div id="fileName" class="file-name d-none"></div> + <input type="file" id="cvFile" class="d-none" accept=".pdf,.doc,.docx"> + <input type="hidden" id="cv" name="cv" required> + </div> + <small class="text-muted">Accepted formats: PDF, DOC, DOCX</small> + <div class="invalid-feedback">Please upload your resume</div> + </div> - <!-- Niveau de qualification --> - <div class="form-group"> - <label for="qualificationLevel">Niveau de qualification :</label> - <select id="qualificationLevel" name="qualificationLevel" class="form-control" required> - <option value="">Choisir un niveau</option> - <option th:each="qualification : ${qualifications}" th:value="${qualification.id}" th:text="${qualification.label}"></option> - </select> - </div> + <!-- Qualification Level --> + <div class="mb-3"> + <label class="form-label">Qualification Level*</label> + <select class="form-select" name="qualificationLevel" required> + <option value="">-- Select level --</option> + <option th:each="qualification : ${qualifications}" + th:value="${qualification.id}" + th:text="${qualification.label}"></option> + </select> + </div> + <div class="col-md-6"> + <label class="form-label">Publication Date*</label> + <input type="date" class="form-control" name="publicationDate" required + th:value="${application.appdate != null} ? ${#temporals.format(jobOffer.appdate, 'yyyy-MM-dd')} : ${#temporals.format(#temporals.createToday(), 'yyyy-MM-dd')}"/> + </div> - <!-- Secteurs d'activité --> - <div class="form-group"> - <label for="sectors">Secteurs d'activité :</label> - <select id="sectors" name="sectors" class="form-control" multiple required> - <option th:each="sector : ${sectors}" th:value="${sector.id}" th:text="${sector.label}"></option> - </select> - <small class="form-text text-muted">Maintenez la touche Ctrl (Cmd sur Mac) pour sélectionner plusieurs options.</small> - </div> + <!-- Improved Industry Sectors Selection --> + <div class="mb-3"> + <label class="form-label">Industry Sectors*</label> + <div class="mb-2"> + <div th:each="sector : ${sectors}" + class="sector-item" + th:data-id="${sector.id}" + th:text="${sector.label}" + onclick="toggleSector(this)"></div> + </div> + <div id="selectedSectors" class="mb-2">No sectors selected</div> + <input type="hidden" id="sectorIds" name="sectors" required> + <div class="invalid-feedback">Please select at least one sector</div> + </div> - <!-- Bouton de soumission --> - <button type="submit" class="btn btn-submit"> Soumettre</button> - </form> + <!-- Buttons --> + <div class="d-flex justify-content-between mt-4"> + <a th:href="@{/jobs}" class="btn btn-outline-secondary"> + <i class="bi bi-arrow-left me-1"></i> Cancel + </a> + <button type="submit" class="btn favorite_back"> + <i class="bi bi-send me-1"></i> Submit Application + </button> + </div> + </form> + </div> </div> -</body> -</html> + + <script> + // Sector selection management + const selectedSectors = new Set(); + + function toggleSector(element) { + const sectorId = element.getAttribute('data-id'); + + if (selectedSectors.has(sectorId)) { + selectedSectors.delete(sectorId); + element.classList.remove('selected'); + } else { + selectedSectors.add(sectorId); + element.classList.add('selected'); + } + + updateSelectedDisplay(); + } + + function updateSelectedDisplay() { + const displayDiv = document.getElementById('selectedSectors'); + const hiddenInput = document.getElementById('sectorIds'); + + if (selectedSectors.size === 0) { + displayDiv.textContent = 'No sectors selected'; + hiddenInput.value = ''; + } else { + displayDiv.textContent = Array.from(selectedSectors).map(id => { + return document.querySelector(`.sector-item[data-id="${id}"]`).textContent; + }).join(', '); + + hiddenInput.value = Array.from(selectedSectors).join(','); + } + } + + // File upload handling + document.getElementById('cvFile').addEventListener('change', function(e) { + const file = e.target.files[0]; + if (file) { + document.getElementById('cvText').classList.add('d-none'); + document.getElementById('fileName').textContent = file.name; + document.getElementById('fileName').classList.remove('d-none'); + document.getElementById('cv').value = file.name; + } + }); + + // Form validation + (function() { + 'use strict'; + const forms = document.querySelectorAll('.needs-validation'); + + Array.from(forms).forEach(form => { + form.addEventListener('submit', event => { + if (!form.checkValidity()) { + event.preventDefault(); + event.stopPropagation(); + } + form.classList.add('was-validated'); + }, false); + }); + })(); + </script> + </body> +</section> +</html> \ No newline at end of file diff --git a/src/main/resources/templates/baseTemplate/base.html b/src/main/resources/templates/baseTemplate/base.html index f480ca2..264b500 100644 --- a/src/main/resources/templates/baseTemplate/base.html +++ b/src/main/resources/templates/baseTemplate/base.html @@ -7,44 +7,64 @@ <link th:href="@{/css/bootstrap.min.css}" rel="stylesheet"> <link th:href="@{/css/gyj_imt.css}" rel="stylesheet"> <link th:href="@{/css/bootstrap-icons.min.css}" rel="stylesheet"> - <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script> - + <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script> <script th:src="@{/js/bootstrap.bundle.min.js}"></script> <script th:src="@{/js/gyj_imt.js}"></script> <script src="http://localhost:35729/livereload.js"></script> - <link rel="icon" th:href="@{/img/favicon-32x32.png}" sizes="32x32" type="image/png"> <title th:replace="${title}">IMT Atlantique: Get Your Job</title> + <style> + html, body { + height: 100%; + } + + body { + display: flex; + flex-direction: column; + } + + main { + flex: 1 0 auto; + padding-bottom: 2rem; /* Espace avant le footer */ + } + footer { + flex-shrink: 0; + background-color: #f8f9fa; + padding: 1rem 0; + } + </style> </head> -<body> - <nav th:insert="~{/baseTemplate/nav :: fheader}" /> - <main class="container"> - <th:block th:replace="${content}"> - Empty Page - </th:block> - <div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true"> - <div class="modal-dialog" role="document"> - <div class="modal-content"> - <div class="modal-header"> - <h5 class="modal-title" id="exampleModalLabel">Confirmation of deletion</h5> - <button type="button" class="close" data-dismiss="modal" aria-label="Close"> - <span aria-hidden="true">×</span> - </button> - </div> - <div class="modal-body"> - <p class="alert alert-danger">Are you sure you want to <span id="delRecord"> </span> ?</p> - </div> - <div class="modal-footer"> - <button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button> - <a href="" class="btn btn-danger" id="delRef">Delete</a> - </div> +<body class="d-flex flex-column min-vh-100"> +<nav th:insert="~{/baseTemplate/nav :: fheader}" /> + +<main class="container flex-grow-1"> + <th:block th:replace="${content}"> + Empty Page + </th:block> + <div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title" id="exampleModalLabel">Confirmation of deletion</h5> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + </div> + <div class="modal-body"> + <p class="alert alert-danger">Are you sure you want to <span id="delRecord"> </span> ?</p> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button> + <a href="" class="btn btn-danger" id="delRef">Delete</a> </div> </div> </div> - </main> - <footer class="mt-4" th:replace="~{/baseTemplate/footer:: footer}" /> + </div> +</main> + +<footer class="mt-auto py-3 bg-light" th:replace="~{/baseTemplate/footer:: footer}" /> </body> </html> \ No newline at end of file diff --git a/src/main/resources/templates/baseTemplate/footer.html b/src/main/resources/templates/baseTemplate/footer.html index bfe41e5..a6172cc 100644 --- a/src/main/resources/templates/baseTemplate/footer.html +++ b/src/main/resources/templates/baseTemplate/footer.html @@ -1,5 +1,5 @@ <footer th:fragment="ffooter"> - <div id="footer_div" class="row"> + <div id="footer_div" class="row"> <div class="col"> IMT Atlantique - FIP </div> diff --git a/src/main/resources/templates/baseTemplate/head.html b/src/main/resources/templates/baseTemplate/head.html index 28f9bec..e60a2d4 100644 --- a/src/main/resources/templates/baseTemplate/head.html +++ b/src/main/resources/templates/baseTemplate/head.html @@ -18,4 +18,6 @@ <title>IMT Atlantique: Get Your Job</title> </head> <body> + </body> + </html> </div> \ No newline at end of file diff --git a/src/main/resources/templates/baseTemplate/nav.html b/src/main/resources/templates/baseTemplate/nav.html index 3653265..08e5b97 100644 --- a/src/main/resources/templates/baseTemplate/nav.html +++ b/src/main/resources/templates/baseTemplate/nav.html @@ -1,31 +1,20 @@ -//header.html <div th:fragment="fheader"> <div id="header_div" class="row h-10"> <div class="col-2 align-self-start"> <img th:src="@{/img/logo_imt.png}" /> </div> - <div class="col-6 align-self-center"></div> - <div class="col-2 align-self-end"> - <p th:if="${#ctx.session.uid} != null"> - <i class="bi bi-" style="font-size: 2rem; color: white;" th:title="${#ctx.session.usertype}" - th:attrappend="class=${#ctx.session.usertype=='company'?'buildings':'person'}"></i> - <span th:text="${#ctx.session.user.mail}" class="tiny_text" /> - </p> - </div> - - <div class="col-2 align-self-end"> - <th:block th:if="${#ctx.session.uid} != null"> - <a th:if="${#ctx.session.hasMessages == true}" th:href="@{/messages}" title="access to your message"><i - class="bi bi-envelope-at" style="font-size: 2rem; color: white;"></i></a> - <a href="/logout" title="logout from the webapp"><i class="bi bi-box-arrow-in-up" - style="font-size: 2rem; color: white;"></i></a> - </th:block> - <a th:if="${#ctx.session.uid} == null" title="Click to login" href="/login"> - <i class="bi bi-box-arrow-in-down" style="font-size: 2rem; color: white;" - title="login to access your data"></i> + <div class="col-8"></div> + <div class="col-2 text-end "> + <!-- Bouton de connexion/déconnexion seulement --> + <div th:if="${session.loggedInUser != null}"> + <a href="/logout" class="btn text-white fw-bold "> + <i class="bi bi-box-arrow-right"></i> Logout + </a> + </div> + <a th:unless="${session.loggedInUser != null}" href="/login" class="btn text-white fw-bold"> + <i class="bi bi-box-arrow-right"></i> Login </a> </div> - </div> <nav class="navbar navbar-expand-md navbar-light"> <div class="container-fluid"> @@ -41,24 +30,22 @@ <a class="nav-link" th:href="@{/companies}">Companies</a> </li> <li class="nav-item"> - <a class="nav-link" th:href="@{/jobs}">Jobs</a> + <a class="nav-link" th:href="@{/candidates/list}">Candidates</a> </li> <li class="nav-item"> - <a class="nav-link" th:href="@{/candidates/list}">Candidates</a> + <a class="nav-link" th:href="@{/jobs}">Job Offers</a> </li> <li class="nav-item"> <a class="nav-link" th:href="@{/applications/list}">Applications</a> </li> <li class="nav-item"> - <a class="nav-link" th:href="@{/qualificationLevels}">Qualification levels</a> + <a class="nav-link" th:href="@{/sectors}">Sectors</a> </li> <li class="nav-item"> - <a class="nav-link" th:href="@{/sectors}">Sectors</a> + <a class="nav-link" th:href="@{/qualificationLevels}">Qualification Levels</a> </li> </ul> - </div> </div> </nav> - </div> \ No newline at end of file diff --git a/src/main/resources/templates/candidate/candidates-list.html b/src/main/resources/templates/candidate/candidates-list.html index 039447c..283fdde 100644 --- a/src/main/resources/templates/candidate/candidates-list.html +++ b/src/main/resources/templates/candidate/candidates-list.html @@ -1,44 +1,106 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> -<head> - <meta charset="UTF-8"> - <title>Liste des Candidats</title> - <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> -</head> -<body> - <div class="container"> - <h2 class="mt-5">Liste des Candidats</h2> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Candidates</title> +<section> + <head> + <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"> + <style> + .candidate-card { + transition: all 0.3s ease; + border-left: 4px solid transparent; + } + .candidate-card:hover { + transform: translateY(-2px); + border-left-color: #3a7bd5; + background-color: #f8f9fa; + } + .table-container { + border-radius: 8px; + overflow: hidden; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); + } + .actions-cell { + white-space: nowrap; + } + .empty-state-icon { + font-size: 3rem; + opacity: 0.5; + } + </style> + </head> + <body class="bg-light"> - <!-- Message d'erreur --> - <div th:if="${error}" class="alert alert-danger" th:text="${error}"></div> + <div class="container py-4"> + <!-- Header Section --> + <div class="d-flex justify-content-between align-items-center mb-4"> + <h2 class="fw-bold text-black mb-0"> + <i class="bi bi-people me-2"></i> Candidates List + </h2> - <table class="table table-striped mt-3"> - <thead> - <tr> - <th>ID</th> - <th>Nom</th> - <th>Prénom</th> - <th>Ville</th> - <th>Email</th> - <th>Actions</th> - </tr> - </thead> - <tbody> - <tr th:each="candidate : ${candidates}"> - <td th:text="${candidate.id}"></td> - <td th:text="${candidate.lastname}"></td> - <td th:text="${candidate.firstname}"></td> - <td th:text="${candidate.city}"></td> - <td th:text="${candidate.mail}"></td> - <td> - <a th:href="@{/candidates/details/{id}(id=${candidate.id})}" class="btn btn-info btn-sm">Détails</a> - <span th:if="${#ctx.session.uid} != null and ${#ctx.session.uid} == ${candidate.id} ">PEUT SUPPRIMER</span> - </td> - </tr> - </tbody> - </table> + </div> - <a th:href="@{/}" class="btn btn-secondary mt-3">Retour</a> + + <!-- Candidates Table --> + <div class="card shadow-sm table-container"> + <div class="card-body p-0"> + <div class="table-responsive"> + <table class="table table-hover align-middle mb-0" th:if="${not #lists.isEmpty(candidates)}"> + <thead class="table-light"> + <tr> + <th class="ps-4">Last Name</th> + <th>First Name</th> + <th>City</th> + <th>Applications Submitted</th> + <th class="text-end pe-4">Actions</th> + </tr> + </thead> + <tbody> + <tr th:each="candidate : ${candidates}" class="candidate-card"> + <td class="ps-4 fw-semibold" th:text="${candidate.lastname}"></td> + <td th:text="${candidate.firstname}"></td> + <td> + <i class="bi bi-geo-alt text-secondary me-1"></i> + <span th:text="${candidate.city}"></span> + </td> + <td> + <span class="badge favorite_back rounded-pill" + th:text="${candidate.getCandidatAppCount()}"></span> + </td> + <td class="text-end pe-4 actions-cell"> + <div > + <a th:href="@{/candidates/details/{id}(id=${candidate.id})}" + class="btn btn-sm favorite_outline" + aria-label="View candidate details"> + <i class="bi bi-eye"></i> View + </a> + <th:block th:if="${session != null and session.userType == 'Candidate' and session.loggedInUser.id == candidate.id}"> + <a th:href="@{'/candidates/edit/' + ${candidate.id}}" + class="btn btn-sm btn-outline-warning"> + <i class="bi bi-pencil"></i> Edit + </a> + <a th:href="@{'/candidates/delete/' + ${candidate.id}}" + class="btn btn-sm btn-outline-danger" + onclick="return confirm('Are you sure you want to delete this candidate?');"> + <i class="bi bi-trash"></i> Delete + </a> + </th:block> + </div> + </td> + </tr> + </tbody> + </table> + <!-- Empty State --> + <div th:if="${#lists.isEmpty(candidates)}" class="text-center py-5"> + <i class="bi bi bi-people empty-state-icon text-muted"></i> + <h4 class="mt-3 text-muted">No candidates registered</h4> + </div> + </div> + </div> + </div> </div> -</body> + + <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> + </body> +</section> </html> diff --git a/src/main/resources/templates/candidate/confirmation.html b/src/main/resources/templates/candidate/confirmation.html index 8a22d72..bd630bf 100644 --- a/src/main/resources/templates/candidate/confirmation.html +++ b/src/main/resources/templates/candidate/confirmation.html @@ -1,34 +1,97 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> -<head> - <meta charset="UTF-8"> - <title>Confirmation</title> - <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> -</head> -<body> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Confirmation</title> +<section> + <head> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"> + <style> + .confirmation-card { + max-width: 800px; + margin: 2rem auto; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 4px 20px rgba(0,0,0,0.08); + border: none; + } + .confirmation-header { + background: rgb(0, 184, 222); + color: white; + padding: 2rem; + text-align: center; + } + .confirmation-icon { + font-size: 3.5rem; + margin-bottom: 1rem; + } + .info-card { + border-left: 4px solid #00B8DEFF; + background-color: #f8f9fa; + } + .btn-custom { + background: rgb(0, 184, 222); + border: none; + padding: 10px 25px; + font-weight: 500; + transition: all 0.3s; + } + .btn-custom:hover { + transform: translateY(-2px); + box-shadow: 0 4px 15px rgba(79, 172, 254, 0.4); + } + </style> + </head> + <body class="bg-light"> <div class="container"> - <div class="alert alert-success mt-5 text-center"> - <h2>Operation Successful</h2> - <p th:text="${message}"></p> - </div> + <div class="card confirmation-card"> + <div class="confirmation-header"> + <i class="bi bi-check-circle-fill confirmation-icon"></i> + <h2 class="mb-3">Opération réussie</h2> + <p class="lead mb-0" th:text="${message}"></p> + </div> - <!-- Affichage des infos du candidat uniquement si c'est une modification --> - <div th:if="${candidate != null}"> - <h3>Informations mises à jour :</h3> - <table class="table table-bordered"> - <tr><th>ID</th><td th:text="${candidate.id}"></td></tr> - <tr><th>Nom</th><td th:text="${candidate.lastname}"></td></tr> - <tr><th>Prénom</th><td th:text="${candidate.firstname}"></td></tr> - <tr><th>Email</th><td th:text="${candidate.mail}"></td></tr> - <tr><th>Ville</th><td th:text="${candidate.city}"></td></tr> - </table> - </div> + <div class="card-body p-4"> + <!-- Affichage des infos du candidat --> + <div th:if="${candidate != null}" class="mb-4"> + <h4 class="mb-3 text-primary"> + <i class="bi bi-person-badge me-2"></i>Informations mises à jour + </h4> + + <div class="row"> + <div class="col-md-6 mb-3"> + <div class="p-3 info-card"> + <h6 class="text-muted">Identité</h6> + <p class="mb-1"><strong th:text="${candidate.firstname + ' ' + candidate.lastname}"></strong></p> + <small class="text-muted" th:text="'ID: ' + ${candidate.id}"></small> + </div> + </div> + + <div class="col-md-6 mb-3"> + <div class="p-3 info-card"> + <h6 class="text-muted">Coordonnées</h6> + <p class="mb-1" th:text="${candidate.mail}"></p> + <p class="mb-0" th:text="${candidate.city}"></p> + </div> + </div> + </div> + </div> - <!-- Bouton différent selon la situation --> - <div class="text-center"> - <a th:if="${candidate != null}" th:href="@{/candidates/list}" class="btn btn-primary">Retour à la liste des candidats</a> - <a th:if="${candidate == null}" th:href="@{/login}" class="btn btn-primary">Go to Login</a> + <!-- Bouton d'action --> + <div class="text-center mt-4"> + <a th:if="${candidate != null}" + th:href="@{/candidates/list}" + class="btn btn-custom text-white"> + <i class="bi bi-arrow-left me-2"></i>Retour à la liste + </a> + <a th:if="${candidate == null}" + th:href="@{/login}" + class="btn btn-custom text-white"> + <i class="bi bi-box-arrow-in-right me-2"></i>Se connecter + </a> + </div> + </div> </div> </div> -</body> -</html> + </body> +</section> +</html> \ No newline at end of file diff --git a/src/main/resources/templates/candidate/confirmationSupp.html b/src/main/resources/templates/candidate/confirmationSupp.html index a8ebbf0..9563393 100644 --- a/src/main/resources/templates/candidate/confirmationSupp.html +++ b/src/main/resources/templates/candidate/confirmationSupp.html @@ -12,7 +12,7 @@ <p th:text="${message}"></p> </div> - <a th:href="@{/candidates/list}" class="btn btn-primary">Retour à la liste des candidats</a> + <a th:href="@{/candidates/list}" class="btn favorite_back">Retour à la liste des candidats</a> </div> </body> </html> diff --git a/src/main/resources/templates/candidate/details.html b/src/main/resources/templates/candidate/details.html index 7e2ea97..9e7c594 100644 --- a/src/main/resources/templates/candidate/details.html +++ b/src/main/resources/templates/candidate/details.html @@ -1,25 +1,114 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> -<head> - <meta charset="UTF-8"> - <title>Détails du Candidat</title> - <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> -</head> -<body> - <div class="container"> - <h2 class="mt-5">Détails du Candidat</h2> - - <table class="table table-bordered mt-3"> - <tr><th>ID</th><td th:text="${candidate.id}"></td></tr> - <tr><th>Nom</th><td th:text="${candidate.lastname}"></td></tr> - <tr><th>Prénom</th><td th:text="${candidate.firstname}"></td></tr> - <tr><th>Email</th><td th:text="${candidate.mail}"></td></tr> - <tr><th>Ville</th><td th:text="${candidate.city}"></td></tr> - </table> - - <a th:href="@{/candidates/edit/{id}(id=${candidate.id})}" class="btn btn-warning">Modifier</a> - <a th:href="@{/candidates/delete/{id}(id=${candidate.id})}" class="btn btn-danger" onclick="return confirm('Voulez-vous vraiment supprimer ce candidat ?')">Supprimer</a> - <a th:href="@{/candidates/list}" class="btn btn-secondary">Retour</a> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Candidates</title> +<section> + <header class="mb-4"> + <h2 class="fw-bold border-bottom pb-2">Candidate Profile</h2> + </header> + + <!-- Alert Messages --> + <div th:if="${successMessage}" class="alert alert-success alert-dismissible fade show" role="alert"> + <i class="bi bi-check-circle-fill me-2"></i> + <span th:text="${successMessage}"></span> + <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> + </div> + + <div th:if="${errorMessage}" class="alert alert-danger alert-dismissible fade show" role="alert"> + <i class="bi bi-exclamation-triangle-fill me-2"></i> + <span th:text="${errorMessage}"></span> + <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> </div> -</body> -</html> + + <div th:if="${candidate != null}" class="card shadow-sm mb-4"> + <div class="card-body"> + <div class="row g-3"> + <!-- Left Column --> + <div class="col-md-6"> + <h4 class="favorite_color fw-bold mb-3"> + <i class="bi bi-person-badge"></i> Personal Information + </h4> + + <div class="mb-3"> + <label class="form-label fw-semibold">First Name</label> + <div class="form-control-plaintext bg-light p-2 rounded" th:text="${candidate.firstname}"></div> + </div> + + <div class="mb-3"> + <label class="form-label fw-semibold">Last Name</label> + <div class="form-control-plaintext bg-light p-2 rounded" th:text="${candidate.lastname}"></div> + </div> + </div> + + <!-- Right Column --> + <div class="col-md-6"> + <div class="mb-3"> + <label class="form-label fw-semibold">Email</label> + <div class="form-control-plaintext bg-light p-2 rounded" th:text="${candidate.mail}"></div> + </div> + + <div class="mb-3"> + <label class="form-label fw-semibold">City</label> + <div class="form-control-plaintext bg-light p-2 rounded" th:text="${candidate.city}"></div> + </div> + </div> + </div> + + <!-- Job Applications Section --> + <div class="mt-4"> + <h4 class="favorite_color fw-bold mb-3"> + <i class="bi bi-file-earmark-text"></i> Job Applications + </h4> + + <div th:if="${#lists.isEmpty(candidateApplications)}" class="alert alert-info"> + <i class="bi bi-info-circle"></i> No job applications found + </div> + + <div th:unless="${#lists.isEmpty(candidateApplications)}" class="table-responsive"> + <table class="table table-hover align-middle"> + <thead class="table-light"> + <tr> + <th>Application ID</th> + <th>CV Reference</th> + <th>Qualification</th> + <th>Sectors</th> + <th>Applied Date</th> + <th class="text-end">Actions</th> + </tr> + </thead> + <tbody> + <tr th:each="app : ${candidateApplications}"> <!-- Changed from application to app --> + <td th:text="${app.id}"></td> + <td th:text="${app.cv}"></td> + <td th:text="${app.qualificationlevel.label}"></td> + <td> + <div th:each="sector : ${app.sectors}" class="badge favorite_back me-1 mb-1" + th:text="${sector.label}"></div> + </td> + <td th:text="${#temporals.format(app.appdate, 'yyyy-MM-dd')}"></td> + <td class="text-end"> + <span th:if="${session != null and session.userType == 'Candidate' and session.loggedInUser.id == candidate.id}" > + <a th:href="@{'/applications/details/' + ${app.id}}" + class="btn btn-sm btn-outline-secondary me-1"> + <i class="bi bi-eye"></i> View + </a> + <a th:href="@{'/applications/edit/' + ${app.id}}" + class="btn btn-sm btn-outline-warning"> + <i class="bi bi-pencil"></i> Edit + </a> + <a th:href="@{'/applications/delete/' + ${app.id}}" + class="btn btn-sm btn-outline-danger" + onclick="return confirm('Are you sure you want to delete this application?');"> + <i class="bi bi-trash"></i> Delete + </a> + </span> + </td> + </tr> + </tbody> + </table> + </div> + </div> + </div> + </div> + +</section> +</html> \ No newline at end of file diff --git a/src/main/resources/templates/candidate/editCandidate.html b/src/main/resources/templates/candidate/editCandidate.html index 8bc1e65..4bb63f8 100644 --- a/src/main/resources/templates/candidate/editCandidate.html +++ b/src/main/resources/templates/candidate/editCandidate.html @@ -1,46 +1,176 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Candidates</title> +<section> <head> <meta charset="UTF-8"> - <title>Modifier un Candidat</title> + <title>Edit Candidate</title> <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> + <link rel="stylesheet" th:href="@{/css/bootstrap-icons.css}"> + <style> + .profile-card { + max-width: 700px; + margin: 0 auto; + border-radius: 10px; + overflow: hidden; + } + .profile-header { + background: #00B8DEFF; + color: white; + } + .form-control:read-only { + background-color: #f8f9fa; + border-color: #dee2e6; + } + .password-container { + position: relative; + } + .password-toggle { + position: absolute; + right: 10px; + top: 50%; + transform: translateY(-50%); + cursor: pointer; + z-index: 5; + background: none; + border: none; + color: #6c757d; + } + .btn-primary { + background-color: rgb(0, 184, 222); + border-color: rgb(0, 184, 222); + } + </style> </head> -<body> - <div class="container"> - <h2 class="mt-5">Modifier un Candidat</h2> - - <form th:action="@{/candidates/edit}" method="post"> - <input type="hidden" name="id" th:value="${candidate.id}" /> - - <div class="form-group"> - <label>Email :</label> - <!-- <input type="email" name="mail" class="form-control" th:value="${candidate.mail}" required> --> - <input type="email" class="form-control" name="mail" required th:value="${candidate.mail}" readonly /> +<body class="bg-light"> +<div class="container py-4"> + <div class="card profile-card shadow-lg"> + <div class="card-header profile-header py-3"> + <div class="d-flex justify-content-between align-items-center"> + <h2 class="h4 mb-0"> + <i class="bi bi-person-gear me-2"></i> Edit Candidate Profile + </h2> </div> + </div> - <div class="form-group"> - <label>Mot de passe :</label> - <input type="password" name="password" class="form-control" required> + <div class="card-body p-4"> + <!-- Success Message --> + <div th:if="${successMessage != null}" class="alert alert-success alert-dismissible fade show"> + <i class="bi bi-check-circle-fill me-2"></i> + <span th:text="${successMessage}"></span> + <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> </div> - <div class="form-group"> - <label>Nom :</label> - <input type="text" name="lastname" class="form-control" th:value="${candidate.lastname}" required> + <!-- Error Messages (simplified to avoid Thymeleaf errors) --> + <div th:if="${errorMessage != null}" class="alert alert-danger alert-dismissible fade show"> + <i class="bi bi-exclamation-triangle-fill me-2"></i> + <span th:text="${errorMessage}"></span> + <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> </div> - <div class="form-group"> - <label>Prénom :</label> - <input type="text" name="firstname" class="form-control" th:value="${candidate.firstname}" required> - </div> + <form th:action="@{/candidates/edit}" method="post" class="needs-validation" novalidate> + <input type="hidden" name="id" th:value="${candidate.id}" /> - <div class="form-group"> - <label>Ville :</label> - <input type="text" name="city" class="form-control" th:value="${candidate.city}" required> - </div> + <div class="row g-4"> + <!-- Left Column --> + <div class="col-md-6"> + <div class="mb-3"> + <label class="form-label fw-semibold">Email</label> + <input type="email" class="form-control" name="mail" + th:value="${candidate.mail}" readonly> + <small class="text-muted">Email cannot be changed</small> + </div> + + <div class="mb-3"> + <label class="form-label fw-semibold">Password</label> + <div class="password-container"> + <input type="password" id="passwordField" name="password" + class="form-control" + placeholder="Leave blank to keep current password"> + <button type="button" class="password-toggle" onclick="togglePassword()"> + <i class="bi bi-eye"></i> + </button> + </div> + <small class="text-muted">Minimum 8 characters</small> + </div> + </div> + + <!-- Right Column --> + <div class="col-md-6"> + <div class="mb-3"> + <label class="form-label fw-semibold">First Name</label> + <input type="text" name="firstname" class="form-control" + th:value="${candidate.firstname}" required> + </div> + + <div class="mb-3"> + <label class="form-label fw-semibold">Last Name</label> + <input type="text" name="lastname" class="form-control" + th:value="${candidate.lastname}" required> + </div> - <button type="submit" class="btn btn-success mt-3">Enregistrer</button> - <a th:href="@{/candidates/list}" class="btn btn-secondary mt-3">Annuler</a> - </form> + <div class="mb-3"> + <label class="form-label fw-semibold">City</label> + <input type="text" name="city" class="form-control" + th:value="${candidate.city}" required> + </div> + </div> + </div> + + <div class="d-flex justify-content-between mt-4"> + <div> + <a th:href="@{/candidates/list}" class="btn btn-outline-secondary"> + <i class="bi bi-arrow-left"></i> Back to list + </a> + </div> + + <div > + <button type="submit" class="btn btn-primary px-4"> + <i class="bi bi-save me-2"></i> Save Changes + </button> + </div> + </div> + </form> + </div> </div> +</div> + +<script th:src="@{/js/bootstrap.bundle.min.js}"></script> +<script> + // Password toggle function + function togglePassword() { + const passwordField = document.getElementById('passwordField'); + const toggleIcon = document.querySelector('.password-toggle i'); + + if (passwordField.type === 'password') { + passwordField.type = 'text'; + toggleIcon.classList.replace('bi-eye', 'bi-eye-slash'); + } else { + passwordField.type = 'password'; + toggleIcon.classList.replace('bi-eye-slash', 'bi-eye'); + } + } + + // Form validation + document.addEventListener('DOMContentLoaded', function() { + const form = document.querySelector('.needs-validation'); + + form.addEventListener('submit', function(event) { + if (!form.checkValidity()) { + event.preventDefault(); + event.stopPropagation(); + } + form.classList.add('was-validated'); + }, false); + + // Add input validation on blur + form.querySelectorAll('input[required]').forEach(input => { + input.addEventListener('blur', () => { + input.classList.toggle('is-invalid', !input.checkValidity()); + }); + }); + }); +</script> </body> -</html> +</section> +</html> \ No newline at end of file diff --git a/src/main/resources/templates/candidate/signupCandidate.html b/src/main/resources/templates/candidate/signupCandidate.html index f8d74e5..dd6a100 100644 --- a/src/main/resources/templates/candidate/signupCandidate.html +++ b/src/main/resources/templates/candidate/signupCandidate.html @@ -1,17 +1,26 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> -<head> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Candidates</title> +<section> + <head> <meta charset="UTF-8"> <title>Candidate Sign Up</title> <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> + <style> + .btn-primary { + background-color: rgb(0, 184, 222); + border-color: rgb(0, 184, 222); + } + </style> </head> <body> <div class="container"> <h2 class="mt-5">Sign Up as Candidate</h2> - <!-- Affichage des erreurs --> - <div th:if="${error}" class="alert alert-danger" th:text="${error}"></div> - + <!-- Affichage des erreurs de connexion --> + <div th:if="${error}" class="alert alert-danger mt-3" role="alert"> + <p class="mb-0" th:text="${error}"></p> + </div> <!-- Formulaire d'inscription --> <form th:action="@{/candidates/signup}" method="post"> <div class="mb-3"> @@ -35,13 +44,15 @@ <input type="text" class="form-control" id="city" name="city" th:value="${city}" required> </div> - <button type="submit" class="btn btn-primary">Sign Up</button> - </form> + <button type="submit" class="btn btn-primary ">Sign Up</button> + <a th:href="@{/login}" class="btn btn-light text-black " >Annuler</a> + </form> <!-- Lien de retour à la page de connexion --> <p class="mt-3"> Already have an account? <a th:href="@{/login}">Sign in here</a> </p> </div> </body> +</section> </html> diff --git a/src/main/resources/templates/company/companyBase.html b/src/main/resources/templates/company/companyBase.html deleted file mode 100644 index eed3128..0000000 --- a/src/main/resources/templates/company/companyBase.html +++ /dev/null @@ -1,13 +0,0 @@ -<!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org" th:fragment="article(subcontent)" - th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> -<title>Companies</title> -<section> - <header> - <h1>Companies Management</h1> - </header> - <th:block th:insert="${subcontent}"> - </th:block> -</section> - -</html> \ No newline at end of file diff --git a/src/main/resources/templates/company/companyEdit.html b/src/main/resources/templates/company/companyEdit.html index 82ea247..dea5445 100644 --- a/src/main/resources/templates/company/companyEdit.html +++ b/src/main/resources/templates/company/companyEdit.html @@ -1,50 +1,143 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/company/companyBase :: article(~{::article})}"> -<article> - <header> - <h2>Modifier l'entreprise</h2> - </header> - - <!-- Message d'erreur ou de succès --> - <div th:if="${successMessage}" class="alert alert-success"> - <strong>Succès :</strong> <span th:text="${successMessage}"></span> - </div> - - <div th:if="${errorMessage}" class="alert alert-danger"> - <strong>Erreur :</strong> <span th:text="${errorMessage}"></span> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Companies</title> +<section> + <head> + <meta charset="UTF-8"> + <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> + <link rel="stylesheet" th:href="@{/css/bootstrap-icons.css}"> + <style> + .profile-card { + max-width: 800px; + margin: 0 auto; + border-radius: 10px; + overflow: hidden; + } + .profile-header { + background: #00B8DEFF; + color: white; + } + .form-control:read-only { + background-color: #f8f9fa; + border-color: #dee2e6; + } + .btn-primary { + background-color: #00B8DEFF; + border-color: #00B8DEFF; + } + .btn-outline-secondary { + border-color: #6c757d; + color: #6c757d; + } + .form-label { + font-weight: 500; + color: #495057; + } + .text-muted { + font-size: 0.85rem; + } + </style> + </head> + <body class="bg-light"> + <div class="container py-4"> + <div class="card profile-card shadow-lg"> + <div class="card-header profile-header py-3"> + <div class="d-flex justify-content-between align-items-center"> + <h2 class="h4 mb-0"> + <i class="bi bi-building-gear me-2"></i> Edit Company Profile + </h2> + </div> + </div> + + <div class="card-body p-4"> + <!-- Alert Messages --> + <div th:if="${successMessage}" class="alert alert-success alert-dismissible fade show"> + <i class="bi bi-check-circle-fill me-2"></i> + <span th:text="${successMessage}"></span> + <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> + </div> + + <div th:if="${error}" class="alert alert-danger alert-dismissible fade show"> + <i class="bi bi-exclamation-triangle-fill me-2"></i> + <span th:text="${error}"></span> + <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> + </div> + + <form th:action="@{/companies/update}" method="post" class="needs-validation" novalidate> + <input type="hidden" name="id" th:value="${company.id}" /> + + <div class="row g-4"> + <!-- Left Column --> + <div class="col-md-6"> + <div class="mb-3"> + <label class="form-label">Email</label> + <input type="email" class="form-control" name="mail" + th:value="${company.mail}" readonly> + <small class="text-muted">Email cannot be changed</small> + </div> + + <div class="mb-3"> + <label class="form-label">Company Name</label> + <input type="text" class="form-control" name="denomination" + th:value="${company.denomination}" required> + </div> + </div> + + <!-- Right Column --> + <div class="col-md-6"> + <div class="mb-3"> + <label class="form-label">Description</label> + <textarea class="form-control" name="description" + rows="3" required th:text="${company.description}"></textarea> + </div> + + <div class="mb-3"> + <label class="form-label">City</label> + <input type="text" class="form-control" name="city" + th:value="${company.city}" required> + </div> + </div> + </div> + + <div class="d-flex justify-content-between mt-4"> + <div> + <a th:href="@{/companies}" class="btn btn-outline-secondary"> + <i class="bi bi-arrow-left"></i> Back to list + </a> + </div> + <div> + <button type="submit" class="btn btn-primary px-4"> + <i class="bi bi-save me-2"></i> Save Changes + </button> + </div> + </div> + </form> + </div> + </div> </div> - - <form th:action="@{/companies/update}" method="post"> - <!-- ID (caché) --> - <input type="hidden" name="id" th:value="${company.id}" /> - - <!-- Email (readonly) --> - <fieldset class="mb-3"> - <label for="emailid" class="form-label">Email</label>: - <input type="email" id="emailid" class="form-control" name="mail" th:value="${company.mail}" readonly /> - </fieldset> - - <!-- Dénomination --> - <fieldset class="mb-3"> - <label for="nameid" class="form-label">Nom</label>: - <input type="text" id="nameid" class="form-control" name="denomination" th:value="${company.denomination}" required /> - </fieldset> - - <!-- Description --> - <fieldset class="mb-3"> - <label for="descid" class="form-label">Description</label>: - <input type="text" id="descid" class="form-control" name="description" th:value="${company.description}" required /> - </fieldset> - - <!-- Ville --> - <fieldset class="mb-3"> - <label for="cityid" class="form-label">Ville</label>: - <input type="text" id="cityid" class="form-control" name="city" th:value="${company.city}" required /> - </fieldset> - - <!-- Boutons --> - <button type="submit" class="btn btn-success">💾 Enregistrer</button> - <a th:href="@{/companies/view/{id}(id=${company.id})}" class="btn btn-danger">❌ Annuler</a> - </form> -</article> -</html> + + <script th:src="@{/js/bootstrap.bundle.min.js}"></script> + <script> + // Form validation + document.addEventListener('DOMContentLoaded', function() { + const form = document.querySelector('.needs-validation'); + + form.addEventListener('submit', function(event) { + if (!form.checkValidity()) { + event.preventDefault(); + event.stopPropagation(); + } + form.classList.add('was-validated'); + }, false); + + // Add input validation on blur + form.querySelectorAll('input[required], textarea[required]').forEach(input => { + input.addEventListener('blur', () => { + input.classList.toggle('is-invalid', !input.checkValidity()); + }); + }); + }); + </script> + </body> +</section> +</html> \ No newline at end of file diff --git a/src/main/resources/templates/company/companyForm.html b/src/main/resources/templates/company/companyForm.html index e594bca..ca2b0a6 100644 --- a/src/main/resources/templates/company/companyForm.html +++ b/src/main/resources/templates/company/companyForm.html @@ -1,6 +1,7 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/company/companyBase :: article(~{::article})}"> -<article> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Companies</title> +<section> <header> <h2>Company entry form</h2> </header> @@ -10,23 +11,23 @@ <strong>Succès :</strong> <span th:text="${successMessage}"></span> </div> - <!-- ❌ Message d'erreur --> - <div th:if="${errorMessage}" class="alert alert-danger"> - <strong>Erreur :</strong> <span th:text="${errorMessage}"></span> + <!-- Affichage des erreurs de connexion --> + <div th:if="${error}" class="alert alert-danger mt-3" role="alert"> + <p class="mb-0" th:text="${error}"></p> </div> <form th:action="@{/companies/create}" method="post"> <!-- Email --> <fieldset class="mb-3"> <label for="emailid" class="form-label">Email Address</label>: - <input type="email" id="emailid" class="form-control" name="mail" + <input type="email" id="emailid" class="form-control" name="mail" th:value="${company.mail}" required /> </fieldset> <!-- Mot de passe --> <fieldset class="mb-3"> <label for="passwordid" class="form-label">Password</label>: - <input type="password" id="passwordid" class="form-control" name="password" + <input type="password" id="passwordid" class="form-control" name="password" minlength="4" required /> </fieldset> @@ -40,20 +41,20 @@ <!-- 📌 CHAMP DESCRIPTION (Ajouté ici) --> <fieldset class="mb-3"> <label for="descid" class="form-label">Description</label>: - <input type="text" id="descid" class="form-control" name="description" + <input type="text" id="descid" class="form-control" name="description" th:value="${company.description}" required /> </fieldset> <!-- Ville --> <fieldset class="mb-3"> <label for="cityid" class="form-label">City</label>: - <input type="text" id="cityid" class="form-control" name="city" + <input type="text" id="cityid" class="form-control" name="city" th:value="${company.city}" required /> </fieldset> <!-- Boutons --> - <button type="submit" class="btn btn-primary">Enregistrer</button> - <a th:href="@{/companies}" class="btn btn-danger">Annuler</a> + <button type="submit" class="btn favorite_back text-white">Save</button> + <a th:href="@{/login}" class="btn btn-light text-black " >Cancel</a> </form> -</article> +</section> </html> \ No newline at end of file diff --git a/src/main/resources/templates/company/companyList.html b/src/main/resources/templates/company/companyList.html index 76c1e01..5e793d4 100644 --- a/src/main/resources/templates/company/companyList.html +++ b/src/main/resources/templates/company/companyList.html @@ -1,49 +1,108 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> -<head> - <title>Liste des Entreprises</title> - <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> -</head> -<body> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Companies</title> +<section> -<div class="container mt-4"> - <h2 class="text-center mb-4">Liste des Entreprises</h2> + <head> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"> + <style> + .company-card { + transition: all 0.3s ease; + border-left: 4px solid transparent; + } + .company-card:hover { + transform: translateY(-2px); + border-left-color: #00b8de; + background-color: #f8f9fa; + } + .description-cell { + max-width: 300px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .table-container { + border-radius: 8px; + overflow: hidden; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); + } + .content-footer { + margin-top: 5rem; + padding-top: 1rem; + border-top: 1px solid #dee2e6; + } + </style> + </head> + <body class="bg-light"> - <table class="table table-striped table-bordered table-hover"> - <thead class="table-dark"> - <tr> - <th>ID</th> - <th>Dénomination</th> - <th>Ville</th> - <th>Description</th> - <th>Nombre d'Offres</th> - <th>Actions</th> - </tr> - </thead> - <tbody> - <tr th:each="company : ${companies}"> - <td th:text="${company.id}"></td> - <td th:text="${company.denomination}"></td> - <td th:text="${company.city}"></td> - <td th:text="${company.description}"></td> - <td th:text="${company.getJobOfferCount()}"></td> - <td> - <a th:href="@{'/companies/view/' + ${company.id}}" class="btn btn-primary btn-sm">Voir</a> - </td> - </tr> - </tbody> - </table> + <div class="container py-4"> + <!-- Header Section --> + <div class="d-flex justify-content-between align-items-center mb-4"> + <h2 class="fw-bold text-black mb-0"> + <i class=" color-title bi bi-buildings me-2"></i> Lists of Companies + </h2> + </div> - <a th:href="@{/}" class="btn btn-secondary mt-3">🏠 Retour à l'accueil</a> + <!-- Companies Table --> + <div class="card shadow-sm table-container"> + <div class="card-body p-0"> + <div class="table-responsive"> + <table class="table table-hover align-middle mb-0" th:if="${not #lists.isEmpty(companies)}"> + <thead class="table-light"> + <tr> + <th class="ps-4">Company Name</th> + <th>Location</th> + <th>Description</th> + <th>Job Offers</th> + <th class="text-end pe-4">Actions</th> + </tr> + </thead> + <tbody> + <tr th:each="company : ${companies}" class="company-card"> + <td class="ps-4 fw-semibold" th:text="${company.denomination}"></td> + <td> + <i class="bi bi-geo-alt text-secondary me-1"></i> + <span th:text="${company.city}"></span> + </td> + <td class="description-cell" th:text="${company.description}"></td> + <td> + <span class="badge favorite_back rounded-pill" + th:text="${company.getJobOfferCount()}"></span> + </td> + <td class="text-end pe-4"> + <div class="d-flex gap-2 justify-content-end"> + <a th:href="@{'/companies/view/' + ${company.id}}" + class="btn btn favorite_outline" > + <i class="bi bi-eye"></i> View + </a> + <th:block th:if="${session != null and session.userType == 'Company' and session.loggedInUser.id == company.id}"> + <a th:href="@{'/companies/edit/' + ${company.id}}" + class="btn btn-sm btn-outline-warning"> + <i class="bi bi-pencil"></i> Edit + </a> + <a th:href="@{'/companies/delete/' + ${company.id}}" + class="btn btn-sm btn-outline-danger" + onclick="return confirm('Are you sure you want to delete this company offer?');"> + <i class="bi bi-trash"></i> Delete + </a> + </th:block> + </div> + </td> + </tr> + </tbody> + </table> + <!-- Empty State --> + <div th:if="${#lists.isEmpty(companies)}" class="text-center py-5"> + <i class="bi bi-buildings empty-state-icon text-muted"></i> + <h4 class="mt-3 text-muted">No companies registered</h4> + </div> + </div> + </div> + </div> + </div> - - <!-- Vérifie si l'utilisateur est authentifié ET est une entreprise --> - <th:block th:if="${#httpServletRequest != null and #httpServletRequest.getSession(false) != null and #httpServletRequest.getSession().getAttribute('usertype') == 'company'}"> - <a th:href="@{/companies/create}" class="btn btn-success">Sign up as a company</a> - </th:block> - -</div> - -<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> -</body> -</html> + <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> + </body> +</section> +</html> \ No newline at end of file diff --git a/src/main/resources/templates/company/companyView.html b/src/main/resources/templates/company/companyView.html index 7d5f091..39baaca 100644 --- a/src/main/resources/templates/company/companyView.html +++ b/src/main/resources/templates/company/companyView.html @@ -1,123 +1,133 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org" - th:replace="~{/company/companyBase :: article(~{::article})}"> -<article> - <header> - <h2>Company details</h2> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Companies</title> +<section> + <header class="mb-4"> + <h2 class="fw-bold border-bottom pb-2">Company Details</h2> </header> - - <div th:if="${successMessage}" class="alert alert-success"> - <strong>Succès :</strong> <span th:text="${successMessage}"></span> + <!-- Alert Messages --> + <div th:if="${successMessage}" class="alert alert-success alert-dismissible fade show" role="alert"> + <i class="bi bi-check-circle-fill me-2"></i> + <span th:text="${successMessage}"></span> + <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> </div> - - <div th:if="${errorMessage}" class="alert alert-danger"> - <strong>Erreur :</strong> <span th:text="${errorMessage}"></span> + + <div th:if="${errorMessage}" class="alert alert-danger alert-dismissible fade show" role="alert"> + <i class="bi bi-exclamation-triangle-fill me-2"></i> + <span th:text="${errorMessage}"></span> + <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> </div> - <div th:if="${company != null}"> - <form> - <fieldset class="mb-3"> - <label for="nameid" class="form-label">Name</label>: - <input type="text" id="nameid" class="form-control" name="denomination" - th:value="${company.denomination}" readonly /> - </fieldset> - <fieldset class="mb-3"> - <label for="emailid" class="form-label">Email</label>: - <input type="text" id="emailid" class="form-control" name="email" - th:value="${company.email}" readonly /> - </fieldset> - - <fieldset class="mb-3"> - <label for="descid" class="form-label">Description</label>: - <input type="text" id="descid" class="form-control" name="description" - th:value="${company.description}" readonly /> - </fieldset> - <fieldset class="mb-3"> - <label for="cityid" class="form-label">City</label>: - <input type="text" id="cityid" class="form-control" name="city" - th:value="${company.city}" readonly /> - </fieldset> - - - <fieldset class="mb-3"> - <label class="form-label">Nombre d'offres d'emploi publiées :</label> - <input type="text" class="form-control" th:value="${jobOfferCount}" readonly /> - </fieldset> - - <fieldset class="mb-3"> - <h3>📋 Offres d'emploi publiées</h3> - <table class="table table-striped table-bordered"> - <thead class="table-dark"> + <div th:if="${company != null}" class="card shadow-sm mb-4"> + <div class="card-body"> + <div class="row g-3"> + <!-- Left Column --> + <div class="col-md-6"> + <h4 class="favorite_color fw-bold mb-3"> + <i class="bi bi-building"></i> Company Information + </h4> + + <div class="mb-3"> + <label class="form-label fw-semibold">Company Name</label> + <div class="form-control-plaintext bg-light p-2 rounded" th:text="${company.denomination}"></div> + </div> + + <div class="mb-3"> + <label class="form-label fw-semibold">Email</label> + <div class="form-control-plaintext bg-light p-2 rounded" th:text="${company.email}"></div> + </div> + + <div class="mb-3"> + <label class="form-label fw-semibold">City</label> + <div class="form-control-plaintext bg-light p-2 rounded" th:text="${company.city}"></div> + </div> + </div> + + <!-- Right Column (Aligned with left column) --> + <div class="col-md-6"> + <div class="mb-3" style="min-height: 96px"> + <label class="form-label fw-semibold">Description</label> + <div class="form-control-plaintext bg-light p-2 rounded" style="min-height: 96px" th:text="${company.description}"></div> + </div> + + <div class="mb-3"> + <label class="form-label fw-semibold">Job Offers</label> + <div class="form-control-plaintext bg-light p-2 rounded fw-bold" + th:text="${jobOfferCount} + (${jobOfferCount} == 1 ? ' active job' : ' active jobs')"></div> + </div> + </div> + </div> + + <!-- Job Offers Section --> + <div class="mt-4"> + <h4 class="favorite_color fw-bold mb-3"> + <i class="bi bi-briefcase"></i> Current Job Offers + </h4> + + <div th:if="${#lists.isEmpty(jobOffers)}" class="alert alert-info"> + <i class="bi bi-info-circle"></i> No active job openings currently + </div> + + <div th:unless="${#lists.isEmpty(jobOffers)}" class="table-responsive"> + <table class="table table-hover align-middle"> + <thead class="table-light"> <tr> <th>ID</th> - <th>Titre</th> + <th>Position</th> <th>Description</th> - <th>Action</th> + <th>Sectors</th> + <th>Qualification</th> + <th>Posted Date</th> + <th class="text-end">Actions</th> </tr> - </thead> - <tbody> + </thead> + <tbody> <tr th:each="offer : ${jobOffers}"> <td th:text="${offer.id}"></td> <td th:text="${offer.title}"></td> - <td th:text="${offer.taskDescription}"></td> - <td> - <a th:href="@{/jobOffers/view/{id}(id=${offer.id})}" class="btn btn-info">Voir l'offre</a> + <div class="text-truncate" style="max-width: 300px" th:text="${offer.taskDescription}"></div> + </td> + <td> + <div class="d-flex flex-wrap"> + <span th:each="sector : ${offer.sectors}" + class="badge favorite_back sector-badge" + th:text="${sector.label}"></span> + </div> + </td> + <td th:text="${offer.qualificationLevel.label}"></td> + <td th:text="${offer.publicationDate != null} ? ${#temporals.format(offer.publicationDate, 'yyyy-MM-dd')} : 'N/A'"></td> + <td class="text-end" > + <a th:href="@{/jobs/view/{id}(id=${offer.id})}" th:if="${session != null and session.userType == 'Company' and session.loggedInUser.id == company.id}" + class="btn btn-sm text-black"> + <i class="bi bi-eye"></i> View + </a> + <th:block th:if="${session != null and session.userType == 'Company' and session.loggedInUser.id == company.id}"> + <a th:href="@{'/jobs/edit/' + ${offer.id}}" + class="btn btn-sm btn-outline-warning"> + <i class="bi bi-pencil"></i> Edit + </a> + <a th:href="@{'/jobs/delete/' + ${offer.id}}" + class="btn btn-sm btn-outline-danger" + onclick="return confirm('Are you sure you want to delete this job offer?');"> + <i class="bi bi-trash"></i> Delete + </a> + </th:block> </td> </tr> - <tr th:if="${#lists.isEmpty(jobOffers)}"> - <td colspan="4" class="text-center text-danger">⚠️ Aucune offre d'emploi trouvée.</td> - </tr> - </tbody> - </table> - </fieldset> - - -<!-- <fieldset class="mb-3"> - <h3>📋 Offres d'emploi publiées</h3> - <table class="table table-striped table-bordered"> - <thead class="table-dark"> - <tr> - <th>ID</th> - <th>Titre</th> - <th>Description</th> - </tr> - </thead> - <tbody> - <tr th:each="offer : ${jobOffers}"> - <td th:text="${offer.id}"></td> - <td th:text="${offer.title}"></td> - <td th:text="${offer.taskDescription}"></td> - </tr> - <tr th:if="${#lists.isEmpty(jobOffers)}"> - <td colspan="3" class="text-center text-danger">⚠️ Aucune offre d'emploi trouvée.</td> - </tr> - </tbody> - </table> -</fieldset> --> -</form> - + </tbody> + </table> + </div> + </div> + </div> </div> - <div th:if="${company == null}"> - <p class="text-danger">❌ Erreur : Aucune entreprise trouvée.</p> - </div> - <footer> - <a th:href="@{/companies/{id}/edit(id=${company.id})}" class="btn btn-primary" title="Modifier"> - ✏️ Modifier - </a> - <!-- <a th:href="@{/companies/{id}/jobOffers(id=${company.id})}" class="btn btn-info"> - 📄 Voir les offres publiées - </a> --> - - - <a th:href="@{/companies/delete/{id}(id=${company.id})}" class="btn btn-danger" - onclick="return confirm('⚠️ Êtes-vous sûr de vouloir supprimer cette entreprise ?');"> - 🗑 Supprimer - </a> - - </footer> -</article> -</html> + + + <div class="mt-3"> + + </div> +</section> +</html> \ No newline at end of file diff --git a/src/main/resources/templates/error/accessDenied.html b/src/main/resources/templates/error/accessDenied.html index d4ed95d..6ddc6e7 100644 --- a/src/main/resources/templates/error/accessDenied.html +++ b/src/main/resources/templates/error/accessDenied.html @@ -9,7 +9,7 @@ <div class="container mt-5 text-center"> <h2 class="text-danger">⛔ Accès interdit</h2> <p>Vous n'avez pas les permissions nécessaires pour voir cette page.</p> - <a href="/" class="btn btn-primary">Retour à l'accueil</a> + <a href="/" class="btn favorite_back">Retour à l'accueil</a> </div> </body> diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index d53f468..347b164 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -59,6 +59,31 @@ </a> </article> --> + + <!-- Nouvelle section profil améliorée --> + <div th:if="${session.loggedInUser != null}" > + + <div class="d-flex justify-content-between align-items-center text-black"> + <div> + <h2> + <i th:classappend="${session.userType == 'Company'} ? 'bi-building' : 'bi-person'" + class="bi me-2"></i> + WELCOME, + <span th:if="${session.userType == 'Company'}"> + [[${session.loggedInUser.denomination}]] + </span> + <span th:if="${session.userType == 'Candidate'}"> + [[${session.loggedInUser.firstname}]] [[${session.loggedInUser.lastname}]] + </span> + </h2> + </div> + <div> + <a th:if="${session.hasMessages}" th:href="@{/messages}" class="btn btn-outline-light me-2"> + <i class="bi bi-envelope-at"></i> Messages + </a> + </div> + </div> + </div> <div class="row row-cols-1 row-cols-lg-3 align-items-stretch g-4 py-5"> <article class="col"> <a th:href="@{/companies}" class="nav-link card card-cover h-100 overflow-hidden text-white bg-dark rounded-5 shadow-lg" @@ -70,7 +95,7 @@ </article> <article class="col"> - <a th:href="@{/candidates}" class="nav-link card card-cover h-100 overflow-hidden text-white bg-dark rounded-5 shadow-lg" + <a th:href="@{/candidates/list}" class="nav-link card card-cover h-100 overflow-hidden text-white bg-dark rounded-5 shadow-lg" style="background-image: url('/img/candidates.jpg'); background-size: cover;"> <div class="d-flex flex-column h-100 p-5 pb-3 text-white text-shadow-1"> <h2 class="pt-5 mt-5 mb-4 display-6 lh-1 fw-bold">Candidates</h2> @@ -79,7 +104,7 @@ </article> <!-- ✅ Déplacement de "Postuler" dans la même ligne --> - <article class="col"> + <article class="col" th:if="${session!=null and session.userType=='Candidate'} "> <a th:href="@{/applications/apply}" class="nav-link card card-cover h-100 overflow-hidden text-white bg-dark rounded-5 shadow-lg" style="background-image: url('/img/postuler.jpeg'); background-size: cover;"> <div class="d-flex flex-column h-100 p-5 pb-3 text-white text-shadow-1"> diff --git a/src/main/resources/templates/jobOffer/companyJobOfferView.html b/src/main/resources/templates/jobOffer/companyJobOfferView.html index 37cb1c2..1ce5550 100644 --- a/src/main/resources/templates/jobOffer/companyJobOfferView.html +++ b/src/main/resources/templates/jobOffer/companyJobOfferView.html @@ -1,19 +1,22 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Create Job Offer</title> +<section> <head> <title>Détails de l'Offre d'Emploi</title> <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> </head> <body> <div class="container"> - <h2>Offre d'Emploi : <span th:text="${jobOffer.title}"></span></h2> + <h2>Offre d'Emploi : <span th:text="${offer.title}"></span></h2> - <p><strong>ID:</strong> <span th:text="${jobOffer.id}"></span></p> - <p><strong>Titre:</strong> <span th:text="${jobOffer.title}"></span></p> - <p><strong>Description:</strong> <span th:text="${jobOffer.taskDescription}"></span></p> - <p><strong>Date de Publication:</strong> <span th:text="${jobOffer.publicationDate}"></span></p> + <p><strong>ID:</strong> <span th:text="${offer.id}"></span></p> + <p><strong>Titre:</strong> <span th:text="${offer.title}"></span></p> + <p><strong>Description:</strong> <span th:text="${offer.taskDescription}"></span></p> + <p><strong>Date de Publication:</strong> <span th:text="${offer.publicationDate}"></span></p> <a th:href="@{/companies}" class="btn btn-secondary">Retour aux entreprises</a> </div> </body> +</section> </html> diff --git a/src/main/resources/templates/jobOffer/jobOfferEdit.html b/src/main/resources/templates/jobOffer/jobOfferEdit.html new file mode 100644 index 0000000..0806a8b --- /dev/null +++ b/src/main/resources/templates/jobOffer/jobOfferEdit.html @@ -0,0 +1,217 @@ +<!DOCTYPE html> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Edit Job Offer</title> +<section> + <head> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"> + <style> + .form-container { + max-width: 800px; + margin: 0 auto; + background-color: #fff; + border-radius: 8px; + padding: 2rem; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + } + .sector-item { + display: inline-block; + margin: 5px; + cursor: pointer; + padding: 5px 10px; + border-radius: 4px; + background: #f8f9fa; + border: 1px solid #dee2e6; + transition: all 0.3s ease; + } + .sector-item.selected { + background: rgb(0, 184, 222); + color: white; + border-color: rgb(0, 184, 222); + } + #selectedSectorsDisplay { + min-height: 40px; + border: 1px solid #ced4da; + border-radius: 4px; + padding: 8px; + margin-top: 5px; + background-color: #f8f9fa; + } + .selected-sector-tag { + display: inline-block; + background: rgb(0, 184, 222); + color: white; + padding: 2px 8px; + border-radius: 4px; + margin-right: 5px; + margin-bottom: 5px; + } + .btn-primary { + background-color: rgb(0, 184, 222); + border-color: rgb(0, 184, 222); + } + </style> + </head> + <body class="bg-light"> + + <div class="container py-4"> + <div class="form-container"> + <h2 class="text-center mb-4"> + <i class="bi bi-briefcase me-2"></i> + Edit Job Offer + </h2> + + <form th:action="@{/jobs/update}" method="post" class="needs-validation" novalidate> + <input type="hidden" name="id" th:value="${job.id}" /> + + <!-- Champs de base --> + <div class="mb-3"> + <label class="form-label">Job Title*</label> + <input type="text" class="form-control" th:value="${job.title}" name="title" required> + </div> + + <div class="mb-3"> + <label class="form-label">Description*</label> + <textarea class="form-control" rows="4" name="taskDescription" required + th:text="${job.taskDescription}"></textarea> + </div> + + <div class="row mb-3"> + <div class="col-md-6"> + <label class="form-label">Publication Date*</label> + <input type="date" class="form-control" name="publicationDate" required + th:value="${job.publicationDate != null} ? ${#temporals.format(job.publicationDate, 'yyyy-MM-dd')} : ${#temporals.format(#temporals.createToday(), 'yyyy-MM-dd')}"/> + </div> + <div class="col-md-6"> + <label class="form-label">Qualification Level*</label> + <select class="form-select" name="qualificationLevel" required> + <option value="">-- Select level --</option> + <option th:each="level : ${qualificationLevels}" + th:value="${level.id}" + th:text="${level.label}" + th:selected="${job.qualificationLevel?.id == level.id}"> + </option> + </select> + </div> + </div> + + <!-- Industry Sectors - Version corrigée --> + <div class="mb-3"> + <label class="form-label">Industry Sectors*</label> + + <!-- Liste complète des secteurs --> + <div class="mb-2"> + <div th:each="sector : ${sectors}" + class="sector-item" + th:data-id="${sector.id}" + th:classappend="${#lists.contains(job.sectors.![id], sector.id)} ? 'selected' : ''" + th:text="${sector.label}" + onclick="toggleSector(this)"></div> + </div> + + <!-- Champ affichant les secteurs sélectionnés --> + <div id="selectedSectorsDisplay" class="mb-2"> + <span th:if="${job.sectors.empty}">No sectors selected</span> + <th:block th:each="sector : ${job.sectors}"> + <div th:data-id="${sector.id}"> + <span th:text="${sector.label}"></span> + </div> + </th:block> + </div> + + <input type="hidden" id="sectorIds" name="sectorIds" + th:value="${#strings.listJoin(job.sectors.![id], ',')}" required> + <div class="invalid-feedback">Please select at least one sector</div> + </div> + + <div class="d-flex justify-content-between mt-4"> + <a th:href="@{/jobs}" class="btn btn-outline-secondary"> + <i class="bi bi-arrow-left me-1"></i> Cancel + </a> + <button type="submit" class="btn btn-primary"> + <i class="bi bi-save me-1"></i> Save Changes + </button> + </div> + </form> + </div> + </div> + + <script> + // Initialize with already selected sectors from the job object + const selectedSectors = new Set( + document.getElementById('sectorIds').value.split(',').filter(Boolean) + ); + + // Mark selected sectors on page load + document.addEventListener('DOMContentLoaded', function() { + updateSelectedDisplay(); + + // Mark initially selected sectors in the list + selectedSectors.forEach(sectorId => { + const element = document.querySelector(`.sector-item[data-id="${sectorId}"]`); + if (element) { + element.classList.add('selected'); + } + }); + }); + + function toggleSector(element) { + const sectorId = element.getAttribute('data-id'); + const sectorLabel = element.textContent; + + if (selectedSectors.has(sectorId)) { + selectedSectors.delete(sectorId); + element.classList.remove('selected'); + } else { + selectedSectors.add(sectorId); + element.classList.add('selected'); + } + + updateSelectedDisplay(); + } + + function updateSelectedDisplay() { + const displayDiv = document.getElementById('selectedSectorsDisplay'); + const hiddenInput = document.getElementById('sectorIds'); + + // Clear the display + displayDiv.innerHTML = ''; + + if (selectedSectors.size === 0) { + displayDiv.innerHTML = '<span>No sectors selected</span>'; + } else { + // Add selected sectors as tags + selectedSectors.forEach(sectorId => { + const sectorElement = document.querySelector(`.sector-item[data-id="${sectorId}"]`); + if (sectorElement) { + const tag = document.createElement('div'); + tag.setAttribute('data-id', sectorId); + tag.innerHTML = `<span>${sectorElement.textContent}</span>`; + displayDiv.appendChild(tag); + } + }); + } + + // Update hidden input with selected sector IDs + hiddenInput.value = Array.from(selectedSectors).join(','); + } + + // Form validation + (function() { + 'use strict'; + const forms = document.querySelectorAll('.needs-validation'); + + Array.from(forms).forEach(form => { + form.addEventListener('submit', event => { + if (!form.checkValidity()) { + event.preventDefault(); + event.stopPropagation(); + } + form.classList.add('was-validated'); + }, false); + }); + })(); + </script> + </body> +</section> +</html> \ No newline at end of file diff --git a/src/main/resources/templates/jobOffer/jobOfferForm.html b/src/main/resources/templates/jobOffer/jobOfferForm.html index 7d27094..a6c538b 100644 --- a/src/main/resources/templates/jobOffer/jobOfferForm.html +++ b/src/main/resources/templates/jobOffer/jobOfferForm.html @@ -1,70 +1,164 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> -<head> - <title>Créer/Modifier une offre d'emploi</title> - <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> -</head> -<body> - -<div class="container mt-4"> - <h2 class="text-center mb-4">Créer une offre d'emploi</h2> - - <!-- ✅ Affichage des messages d'erreur --> - <div th:if="${errorMessage}" class="alert alert-danger text-center"> - <span th:text="${errorMessage}"></span> - </div> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Create Job Offer</title> +<section> + <head> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"> + <style> + .form-container { + max-width: 800px; + margin: 0 auto; + background-color: #fff; + border-radius: 8px; + padding: 2rem; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + } + .sector-item { + display: inline-block; + margin: 5px; + cursor: pointer; + padding: 5px 10px; + border-radius: 4px; + background: #f8f9fa; + border: 1px solid #dee2e6; + } + .sector-item.selected { + background: rgb(0, 184, 222); + color: white; + border-color: rgb(0, 184, 222); + } + #selectedSectors { + min-height: 40px; + border: 1px solid #ced4da; + border-radius: 4px; + padding: 8px; + margin-top: 5px; + } + .hidden-input { + display: none; + } + .btn-primary { + background-color: rgb(0, 184, 222); + border-color: rgb(0, 184, 222); + } + </style> + </head> + <body class="bg-light"> - <form th:action="@{${jobOffer.id != null} ? '/jobs/update/' + jobOffer.id : '/jobs/save'}" - th:object="${jobOffer}" method="post"> + <div class="container py-4"> + <div class="form-container"> + <h2 class="text-center mb-4"> + <i class="bi bi-briefcase me-2"></i> + Create Job Offer + </h2> - <input type="hidden" th:field="*{id}" /> + <form th:action="@{/jobs/save}" method="post" class="needs-validation" novalidate> - <div class="mb-3"> - <label class="form-label">Titre :</label> - <input type="text" class="form-control" th:field="*{title}" required> - </div> + <!-- Champs de base --> + <div class="mb-3"> + <label class="form-label">Job Title*</label> + <input type="text" class="form-control" name="title" required> + </div> - <div class="mb-3"> - <label class="form-label">Description :</label> - <textarea class="form-control" rows="3" th:field="*{taskDescription}" required></textarea> - </div> + <div class="mb-3"> + <label class="form-label">Description*</label> + <textarea class="form-control" rows="4" name="taskDescription" required></textarea> + </div> - <div class="mb-3"> - <label class="form-label">Date de publication :</label> - <input type="date" class="form-control" th:field="*{publicationDate}" required> - </div> + <div class="row mb-3"> + <div class="col-md-6"> + <label class="form-label">Publication Date*</label> + <input type="date" class="form-control" name="publicationDate" required + th:value="${jobOffer.publicationDate != null} ? ${#temporals.format(jobOffer.publicationDate, 'yyyy-MM-dd')} : ${#temporals.format(#temporals.createToday(), 'yyyy-MM-dd')}"/> + </div> + <div class="col-md-6"> + <label class="form-label">Qualification Level*</label> + <select class="form-select" name="qualificationLevel" required> + <option value="">-- Select level --</option> + <option th:each="level : ${qualificationLevels}" + th:value="${level.id}" + th:text="${level.label}"></option> + </select> + </div> + </div> - <div class="mb-3"> - <label class="form-label">Entreprise :</label> - <select class="form-select" th:field="*{company}" required> - <option value="">-- Sélectionner une entreprise --</option> - <option th:each="company : ${companies}" th:value="${company.id}" th:text="${company.denomination}"></option> - </select> - </div> + <!-- Sélection des secteurs --> + <div class="mb-3"> + <label class="form-label">Industry Sectors*</label> + <div class="mb-2"> + <div th:each="sector : ${sectors}" + class="sector-item" + th:data-id="${sector.id}" + th:text="${sector.label}" + onclick="toggleSector(this)"></div> + </div> + <div id="selectedSectors" class="mb-2">No sectors selected</div> + <input type="hidden" id="sectorIds" name="sectorIds" required> + <div class="invalid-feedback">Please select at least one sector</div> + </div> - <div class="mb-3"> - <label class="form-label">Niveau de qualification :</label> - <select class="form-select" th:field="*{qualificationLevel}"> - <option value="0">-- Sélectionner un niveau de qualification --</option> - <option th:each="level : ${qualificationLevels}" th:value="${level.id}" th:text="${level.label}"></option> - </select> + <div class="d-flex justify-content-between mt-4"> + <a href="/jobs" class="btn btn-outline-secondary"> + <i class="bi bi-arrow-left me-1"></i> Cancel + </a> + <button type="submit" class="btn btn-primary"> + <i class="bi bi-save me-1"></i> Create Offer + </button> + </div> + </form> </div> + </div> - <div class="mb-3"> - <label class="form-label">Secteurs d'activité :</label> - <select class="form-select" multiple th:name="sectorIds"> - <option th:each="sector : ${sectors}" th:value="${sector.id}" th:text="${sector.label}"></option> - </select> - </div> + <script> + const selectedSectors = new Set(); - <div class="text-center"> - <button type="submit" class="btn btn-success">Enregistrer</button> - <a href="/jobs" class="btn btn-secondary">Retour</a> - </div> + function toggleSector(element) { + const sectorId = element.getAttribute('data-id'); + + if (selectedSectors.has(sectorId)) { + selectedSectors.delete(sectorId); + element.classList.remove('selected'); + } else { + selectedSectors.add(sectorId); + element.classList.add('selected'); + } + + updateSelectedDisplay(); + } + + function updateSelectedDisplay() { + const displayDiv = document.getElementById('selectedSectors'); + const hiddenInput = document.getElementById('sectorIds'); + + if (selectedSectors.size === 0) { + displayDiv.textContent = 'No sectors selected'; + hiddenInput.value = ''; + } else { + displayDiv.textContent = Array.from(selectedSectors).map(id => { + return document.querySelector(`.sector-item[data-id="${id}"]`).textContent; + }).join(', '); + + hiddenInput.value = Array.from(selectedSectors).join(','); + } + } - </form> -</div> + // Form validation + (function() { + 'use strict'; + const forms = document.querySelectorAll('.needs-validation'); -<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> -</body> -</html> + Array.from(forms).forEach(form => { + form.addEventListener('submit', event => { + if (!form.checkValidity()) { + event.preventDefault(); + event.stopPropagation(); + } + form.classList.add('was-validated'); + }, false); + }); + })(); + </script> + </body> +</section> +</html> \ No newline at end of file diff --git a/src/main/resources/templates/jobOffer/jobOfferList.html b/src/main/resources/templates/jobOffer/jobOfferList.html index ea6688d..228e5a0 100644 --- a/src/main/resources/templates/jobOffer/jobOfferList.html +++ b/src/main/resources/templates/jobOffer/jobOfferList.html @@ -1,85 +1,123 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Jobs</title> +<section> <head> - <title>Liste des Offres d'Emploi</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"> + <style> + .job-card { + transition: all 0.3s ease; + border-left: 4px solid transparent; + } + .job-card:hover { + transform: translateY(-2px); + border-left-color: #00b8de; + background-color: #f8f9fa; + } + .table-container { + border-radius: 8px; + overflow: hidden; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); + } + .sector-badge { + margin-right: 4px; + margin-bottom: 4px; + } + .empty-state-icon { + font-size: 3rem; + opacity: 0.5; + } + </style> </head> -<body> +<body class="bg-light"> -<div class="container mt-4"> - <h2 class="text-center mb-4">Liste des Offres d'Emploi</h2> - - <div th:if="${successMessage}" class="alert alert-success"> - <p th:text="${successMessage}"></p> - </div> - <div th:if="${errorMessage}" class="alert alert-danger"> - <p th:text="${errorMessage}"></p> +<div class="container py-4"> + <!-- Header Section --> + <div class="d-flex justify-content-between align-items-center mb-4"> + <h2 class="fw-bold text-black mb-0"> + <i class="bi bi-briefcase me-2"></i> Job Offers + </h2> + <a th:href="@{/jobs/create}" class="btn btn-outline-secondary" th:if="${session != null and session.userType == 'Company'}"> + <i class="bi bi-plus-circle"></i> Create Job Offer + </a> </div> - <table class="table table-striped table-bordered table-hover"> - <thead class="table-dark"> - <tr> - <th>ID</th> - <th>Titre</th> - <th>Entreprise</th> - <th>Secteurs</th> - <th>Actions</th> - </tr> - </thead> - <tbody> - <tr th:each="job : ${jobOffers}"> - <td th:text="${job.id}"></td> - <td th:text="${job.title}"></td> - <td th:text="${job.company.denomination}"></td> - <td> - <ul class="list-unstyled"> - <li th:each="sector : ${job.sectors}" th:text="${sector.label}"></li> - </ul> - </td> - <td> - <a th:href="@{'/jobs/view/' + ${job.id}}" class="btn btn-primary btn-sm">Voir</a> - - <!-- Vérification si l'utilisateur connecté est une entreprise ET qu'il est le propriétaire de l'offre --> - <th:block th:if="${session.loggedInUser != null - and session.loggedInUser.usertype == 'company' - and session.loggedInUser.mail == job.company.mail}"> - <a th:href="@{'/jobs/' + ${job.id} + '/edit'}" class="btn btn-warning btn-sm">Modifier</a> - <a th:href="@{'/jobs/delete/' + ${job.id}}" - class="btn btn-danger btn-sm" - onclick="return confirm('Êtes-vous sûr de vouloir supprimer cette offre ?');"> - Supprimer - </a> - </th:block> - <a th:href="@{'/jobs/' + ${job.id} + '/edit'}" class="btn btn-warning btn-sm">Modifier</a> - <a th:href="@{'/jobs/delete/' + ${job.id}}" - class="btn btn-danger btn-sm" - onclick="return confirm('Êtes-vous sûr de vouloir supprimer cette offre ?');"> - Supprimer - </a> - </th:block> - </td> - </tr> - </tbody> - - - </table> - <div class="text-center mt-4"> - <a href="/jobs/create" class="btn btn-success">Créer une nouvelle offre</a> + <!-- Alert Messages --> + <div th:if="${successMessage}" class="alert alert-success alert-dismissible fade show"> + <i class="bi bi-check-circle-fill me-2"></i> + <span th:text="${successMessage}"></span> + <button type="button" class="btn-close" data-bs-dismiss="alert"></button> </div> - - <th:block th:if="${session.usertype != null and session.usertype == 'company'}"> - <a href="/jobs/create" class="btn btn-success">Créer une nouvelle offre</a> -</th:block> + <!-- Job Offers Table --> + <div class="card shadow-sm table-container"> + <div class="card-body p-0"> + <div class="table-responsive"> + <table class="table table-hover align-middle mb-0" th:if="${not #lists.isEmpty(jobOffers)}"> + <thead class="table-light"> + <tr> + <th class="ps-4">ID</th> + <th>Title</th> + <th>Company</th> + <th>Sectors</th> + <th>Qualification</th> + <th>Posted Date</th> + <th class="text-end pe-4">Actions</th> + </tr> + </thead> + <tbody> + <tr th:each="job : ${jobOffers}" class="job-card"> + <td class="ps-4" th:text="${job.id}"></td> + <td class="fw-semibold" th:text="${job.title}"></td> + <td th:text="${job.company?.denomination} ?: 'N/A'"></td> + <td> + <div class="d-flex flex-wrap"> + <span th:each="sector : ${job.sectors}" + class="badge favorite_back sector-badge" + th:text="${sector.label}"></span> + </div> + </td> + <td th:text="${job.qualificationLevel.label}"></td> + <td th:text="${job.publicationDate != null} ? ${#temporals.format(job.publicationDate, 'yyyy-MM-dd')} : 'N/A'"></td> <td class="text-end pe-4"> + <div class="d-flex gap-2 justify-content-end"> + <a th:href="@{'/jobs/view/' + ${job.id}}" + class="btn btn-sm favorite_outline"> + <i class="bi bi-eye"></i> View + </a> + <th:block th:if="${session != null and session.userType == 'Company' and session.loggedInUser.id == job.company.id}"> + <a th:href="@{'/jobs/edit/' + ${job.id}}" + class="btn btn-sm btn-outline-warning"> + <i class="bi bi-pencil"></i> Edit + </a> + <a th:href="@{'/jobs/delete/' + ${job.id}}" + class="btn btn-sm btn-outline-danger" + onclick="return confirm('Are you sure you want to delete this job offer?');"> + <i class="bi bi-trash"></i> Delete + </a> + </th:block> + </div> + </td> + </tr> + </tbody> + </table> + + <!-- Empty State --> + <div th:if="${#lists.isEmpty(jobOffers)}" class="text-center py-5"> + <i class="bi bi-briefcase empty-state-icon text-muted"></i> + <h4 class="mt-3 text-muted">No job offers found</h4> + </div> + </div> + </div> + </div> - </div> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> </body> +</section> </html> - @@ -137,8 +175,31 @@ <a href="/jobs/create" class="btn btn-success">Créer une nouvelle offre</a> <a th:href="@{/}" class="btn btn-secondary mt-3">🏠 Retour à l'accueil</a> + </div> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> </body> -</html> --> +</html> + <td> + <a th:href="@{'/jobs/view/' + ${job.id}}" class="btn btn-primary btn-sm">Voir</a> + +<th:block th:if="${session.loggedInUser != null + and session.loggedInUser.usertype == 'COMPANY' + and session.loggedInUser.mail == job.company.mail}"> + <a th:href="@{'/jobs/' + ${job.id} + '/edit'}" class="btn btn-warning btn-sm">Modifier</a> + <a th:href="@{'/jobs/delete/' + ${job.id}}" + class="btn btn-danger btn-sm" + onclick="return confirm('Êtes-vous sûr de vouloir supprimer cette offre ?');"> + Supprimer + </a> +</th:block> +<a th:href="@{'/jobs/' + ${job.id} + '/edit'}" class="btn btn-warning btn-sm">Modifier</a> +<a th:href="@{'/jobs/delete/' + ${job.id}}" + class="btn btn-danger btn-sm" + onclick="return confirm('Êtes-vous sûr de vouloir supprimer cette offre ?');"> + Supprimer +</a> +</th:block> +</td> + --> diff --git a/src/main/resources/templates/jobOffer/jobOfferView.html b/src/main/resources/templates/jobOffer/jobOfferView.html index e5fbffb..bd9bc09 100644 --- a/src/main/resources/templates/jobOffer/jobOfferView.html +++ b/src/main/resources/templates/jobOffer/jobOfferView.html @@ -1,55 +1,94 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> <head> - <title>Détails de l'Offre d'Emploi</title> + <title>Job Details</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"> + <style> + :root { + --primary: #072262; + --accent: #ff6b35; + } + </style> </head> -<body class="bg-light"> +<body> +<section> + <header class="mb-4"> + <h2 class="fw-bold border-bottom pb-2">Job Details</h2> + </header> -<div class="container mt-5"> - <h2 class="text-center mb-4"> Détails de l'Offre d'Emploi</h2> + <!-- Alert Messages --> + <div th:if="${successMessage}" class="alert alert-success alert-dismissible fade show" role="alert"> + <i class="bi bi-check-circle-fill me-2"></i> + <span th:text="${successMessage}"></span> + <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> + </div> - <div class="card shadow-lg"> - <div class="card-header bg-primary text-white text-center"> - <h4 th:text="${jobOffer.title}"></h4> - </div> + <div th:if="${errorMessage}" class="alert alert-danger alert-dismissible fade show" role="alert"> + <i class="bi bi-exclamation-triangle-fill me-2"></i> + <span th:text="${errorMessage}"></span> + <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> + </div> + + <div th:if="${job != null}" class="card shadow-sm mb-4"> <div class="card-body"> - <p><strong>ID de l'offre :</strong> <span th:text="${jobOffer.id}"></span></p> - <p><strong>Description :</strong> <span th:text="${jobOffer.taskDescription}"></span></p> - <p><strong>Entreprise :</strong> <span th:text="${jobOffer.company.denomination}"></span></p> - <p><strong>Date de publication :</strong> <span th:text="${jobOffer.publicationDate}"></span></p> - - <!-- ✅ Ajout du Niveau de Qualification --> - <p><strong>Niveau de qualification requis :</strong> - <span th:text="${jobOffer.qualificationLevel != null ? jobOffer.qualificationLevel.label : 'Non spécifié'}"></span> - </p> - - <!-- ✅ Section Secteurs améliorée avec des badges --> - <h5 class="mt-3">Secteurs :</h5> - <div class="d-flex flex-wrap"> - <span th:each="sector : ${jobOffer.sectors}" - th:text="${sector.label}" - class="badge bg-success text-white m-1 p-2"> - </span> + <div class="row g-3"> + <!-- Left Column --> + <div class="col-md-6"> + <h4 class="favorite_color fw-bold mb-3"> + <i class="bi bi-briefcase"></i> Job Information + </h4> + + <div class="mb-3"> + <label class="form-label fw-semibold">Job Title</label> + <div class="form-control-plaintext bg-light p-2 rounded" th:text="${job.title}"></div> + </div> + + <div class="mb-3"> + <label class="form-label fw-semibold">Company</label> + <div class="form-control-plaintext bg-light p-2 rounded" + th:text="${job.company?.denomination} ?: 'N/A'"></div> + </div> + + <div class="mb-3"> + <label class="form-label fw-semibold">Qualification Level</label> + <div class="form-control-plaintext bg-light p-2 rounded" + th:text="${job.qualificationLevel.label}"></div> + </div> + </div> + + <!-- Right Column --> + <div class="col-md-6"> + <h4 class="favorite_color fw-bold mb-3"> + <i class="bi bi-calendar-event"></i> Additional Information + </h4> + + <div class="mb-3"> + <label class="form-label fw-semibold">Publication Date</label> + <div class="form-control-plaintext bg-light p-2 rounded" + th:text="${#temporals.format(job.publicationDate, 'MMMM d, yyyy')}"></div> + </div> + + <div th:if="${not #lists.isEmpty(job.sectors)}" class="mb-3"> + <label class="form-label fw-semibold">Sectors</label> + <div class="form-control-plaintext bg-light p-2 rounded"> + <span th:each="sector : ${job.sectors}" class="badge favorite_back me-1" + th:text="${sector.label}"></span> + </div> + </div> + </div> </div> - </div> - </div> - <div class="text-center mt-4"> - <a href="/jobs" class="btn btn-secondary btn-lg"> - ⬅ <i class="bi bi-arrow-left"></i> Retour - </a> - <a th:href="@{'/jobs/' + ${jobOffer.id} + '/edit'}" class="btn btn-warning btn-lg"> - ✏ <i class="bi bi-pencil-square"></i> Modifier - </a> - <a th:href="@{'/jobs/delete/' + ${jobOffer.id}}" - class="btn btn-danger btn-lg" - onclick="return confirm('Êtes-vous sûr de vouloir supprimer cette offre ?');"> - 🗑 <i class="bi bi-trash"></i> Supprimer - </a> + <!-- Job Description Section --> + <div class="mt-4"> + <h4 class="favorite_color fw-bold mb-3"> + <i class="bi bi-file-text"></i> Job Description + </h4> + <div class="form-control-plaintext bg-light p-3 rounded" th:utext="${job.taskDescription}"></div> + </div> + </div> </div> -</div> -<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> +</section> </body> -</html> +</html> \ No newline at end of file diff --git a/src/main/resources/templates/login.html b/src/main/resources/templates/login.html index fbaa5e7..658fa74 100644 --- a/src/main/resources/templates/login.html +++ b/src/main/resources/templates/login.html @@ -1,45 +1,161 @@ <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" th:fragment="article(subcontent)" - th:replace="~{/baseTemplate/base :: layout(~{},~{::section})}"> - -<section class="modal modal-sheet position-static d-block bg-body-secondary p-4 py-md-5" tabindex="-1"> - <div class="modal-dialog" role="document"> - <div class="modal-content rounded-4 shadow"> - <header class="modal-header p-5 pb-4 border-bottom-0"> - <h1 class="fw-bold mb-0 fs-2">Sign in</h1> - <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> - </header> - - <article class="modal-body p-5 pt-0"> - <small class="text-secondary">Log in by entering your email address and password.</small> - <form action="/login" method="post"> - <fieldset class="mb-3 form-floating"> - <input type="email" id="uid" class="form-control rounded-3" name="mail" autofocus="autofocus" required - placeholder="name@domain.com" /> - <label for="uid">Email address</label> - </fieldset> - <fieldset class="mb-3 form-floating"> - <input type="password" id="idpwd" class="form-control rounded-3" name="password" placeholder="your password" - required /> - <label for="idpwd">Password</label> - </fieldset> - <input type="submit" value="Sign in" class="w-100 mb-2 btn btn-lg rounded-3 btn-primary" /> - </form> - - <!-- Affichage des erreurs de connexion --> - <div th:if="${error}" class="alert alert-danger mt-3" role="alert"> - <p th:text="${error}"></p> + th:replace="~{/baseTemplate/base :: layout(~{},~{::section})}"> + +<head> + <style> + .favorite_color { + color:rgb(0, 183, 211); + } + + .favorite_back { + background-color: rgb(0, 183, 211); + } + + .favorite_outline { + border-color: rgb(0, 183, 211); + color: rgb(0, 183, 211); + } + + .favorite_outline:hover { + background-color: rgb(0, 183, 211); + color: white; + } + + .login-card { + border: none; + border-radius: 15px; + box-shadow: 0 10px 30px rgba(7, 34, 98, 0.1); + overflow: hidden; + } + + .card-header { + background-color: rgb(0, 183, 211); + color: white; + padding: 1.5rem; + } + + .form-control { + border-radius: 8px; + padding: 12px 15px; + border: 1px solid #e0e0e0; + transition: all 0.3s; + } + + .form-control:focus { + border-color:rgb(0, 183, 211); + box-shadow: 0 0 0 0.25rem rgba(7, 34, 98, 0.25); + } + + .floating-label { + color: #6c757d; + transition: all 0.3s; + } + + .form-floating>.form-control:focus~label, + .form-floating>.form-control:not(:placeholder-shown)~label { + color:rgb(0, 183, 211); + transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); + } + + .login-btn { + padding: 12px; + font-size: 1rem; + letter-spacing: 0.5px; + transition: all 0.3s; + } + + .login-btn:hover { + background-color: rgb(0, 183, 211); + transform: translateY(-2px); + } + + .divider { + display: flex; + align-items: center; + margin: 1.5rem 0; + } + + .divider::before, + .divider::after { + content: ""; + flex: 1; + border-bottom: 1px solid #e0e0e0; + } + + .divider-text { + padding: 0 1rem; + color: #6c757d; + font-size: 0.9rem; + } + + .register-option { + transition: all 0.3s; + padding: 10px; + border-radius: 8px; + } + + .register-option:hover { + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(7, 34, 98, 0.1); + } + + .register-icon { + margin-right: 8px; + } + </style> +</head> + +<section class="container my-5"> + <div class="row justify-content-center"> + <div class="col-md-6 col-lg-5"> + <div class="card login-card"> + <div class="card-header favorite_back text-white text-center"> + <h3 class="mb-0">Welcome Back</h3> + <p class="mb-0 opacity-75">Sign in to your account</p> + </div> + + <div class="card-body p-4"> + <form th:action="@{/login}" method="post"> + <fieldset class="mb-3 form-floating"> + <input type="email" id="uid" class="form-control" name="mail" autofocus="autofocus" required + placeholder="name@domain.com" /> + <label for="uid" class="floating-label">Email address</label> + </fieldset> + + <fieldset class="mb-4 form-floating"> + <input type="password" id="idpwd" class="form-control" name="password" placeholder="your password" + required /> + <label for="idpwd" class="floating-label">Password</label> + </fieldset> + + <input type="submit" value="Sign in" class="w-100 btn login-btn text-black favorite_outline" /> + </form> + + <!-- Affichage des erreurs de connexion --> + <div th:if="${error}" class="alert alert-danger mt-3" role="alert"> + <p class="mb-0" th:text="${error}"></p> + </div> </div> - </article> - - <footer class="modal-body p-5 pt-0"> - <hr class="my-4"> - <a th:href="@{/companies/create}" class="btn w-100 mb-2 btn btn-lg rounded-3 btn-secondary">Sign up as company</a> - <a th:href="@{/candidates/signup}" class="btn w-100 mb-2 btn btn-lg rounded-3 btn-secondary">Sign up as candidate</a> - <small class="text-secondary">By clicking Sign up, you agree to the terms of use.</small> - </footer> + + <div class="px-4 pb-4"> + <div class="divider"> + <span class="divider-text">OR</span> + </div> + + <p class="mb-3 text-center">Don't have an account?</p> + + <div class="d-grid gap-3"> + <a th:href="@{/companies/create}" class="btn favorite_outline register-option"> + <i class="bi bi-building register-icon "></i> Register as Company + </a> + <a th:href="@{/candidates/signup}" class="btn favorite_outline register-option"> + <i class="bi bi-person register-icon"></i> Register as Candidate + </a> + </div> + </div> + </div> </div> </div> </section> - -</html> +</html> \ No newline at end of file diff --git a/src/main/resources/templates/qualificationLevel/qualificationLevelList.html b/src/main/resources/templates/qualificationLevel/qualificationLevelList.html index c97eb59..edadb95 100644 --- a/src/main/resources/templates/qualificationLevel/qualificationLevelList.html +++ b/src/main/resources/templates/qualificationLevel/qualificationLevelList.html @@ -1,45 +1,303 @@ <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> -<title>Qualification Level</title> +<title>Qualification Levels</title> <section> - <header> - <h1>List of qualification levels</h1> - </header> - <article> - <!-- <a href="javascript:history.back()"><img th:src="@{/img/back.png}" alt="Back"/></a> --> - <p th:if="${#lists.size(qualificationlevellist)} == 0">No qualification level defined yet.</p> - <th:block th:if="${#lists.size(qualificationlevellist)} > 0"> - <table class="table table-striped"> - <caption>List of qualification levels</caption> - <thead> - <tr> - <th scope="col">#</th> - <th scope="col">Label</th> - </tr> - </thead> - <tbody> - <tr th:each="ql : ${qualificationlevellist}"> - <th scope="row" th:text="${ql.id}" /> - <td th:text="${ql.label}" /> - <!-- <td th:if="${#ctx.session.uid} != null AND ${#ctx.session.logintype} == 'adm'" class="nav-item active"> - <a th:href="@{/deletequalificationlevel/{id}(id=${ql.id})}"> - <img th:src="@{img/minus.png}" alt="Delete this sector" class="minilogo"/> - </a> - </td> --> - </tr> - </tbody> - </table> - </th:block> - <!-- <div th:if="${#ctx.session.uid} != null AND ${#ctx.session.logintype} == 'adm'" class="row h-10"> - <form action="/addqualificationlevel" method="get" class="col-xs-12 col-sm-6 col-md-4 col-lg-2"> - <label for="labelql">Label</label> - <input type="text" id="labelql" name="labelql" autofocus="autofocus" minlength="3" required/> <br /> - - <input type="submit" value="Add" /> - </form> - </form> - </div> --> - </article> -</section> + <head> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"> + <style> + .qualification-card { + transition: all 0.3s ease; + border-left: 4px solid transparent; + } + .qualification-card:hover { + transform: translateY(-2px); + border-left-color: #3a7bd5; + background-color: #f8f9fa; + } + .table-container { + border-radius: 8px; + overflow: hidden; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); + } + .empty-state { + min-height: 300px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + .search-container { + position: relative; + margin-bottom: 1.5rem; + } + .search-icon { + position: absolute; + left: 12px; + top: 10px; + color: #6c757d; + } + .search-input { + padding-left: 40px; + border-radius: 20px; + border: 1px solid #ced4da; + transition: all 0.3s; + } + .search-input:focus { + box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); + border-color: #86b7fe; + } + .hidden-row { + display: none; + } + .pagination .page-item.active .page-link { + background-color: rgb(0, 184, 222); + border-color: rgb(0, 184, 222); + color: white; + } + .pagination .page-link { + color: rgb(0, 184, 222); + } + .page-size-selector { + width: auto; + display: inline-block; + } + </style> + </head> + <body class="bg-light"> + <div class="container py-4"> + <!-- Header Section --> + <div class="d-flex justify-content-between align-items-center mb-4"> + <h2 class="fw-bold text-black mb-0"> + <i class="bi bi-award me-2"></i> Qualification Levels + </h2> + </div> + + <!-- Search and Page Size Controls --> + <div class="row mb-3"> + <div class="col-md-6"> + <div class="search-container"> + <i class="bi bi-search search-icon"></i> + <input type="text" class="form-control search-input" id="liveSearchInput" + placeholder="Start typing to filter qualification levels..." autocomplete="off"> + </div> + </div> + <div class="col-md-6 text-md-end"> + <div class="d-inline-flex align-items-center"> + <span class="me-2">Show:</span> + <select class="form-select form-select-sm page-size-selector" id="pageSizeSelect"> + <option value="5">5</option> + <option value="10" selected>10</option> + <option value="20">20</option> + <option value="50">50</option> + <option value="100">100</option> + </select> + <span class="ms-2">entries</span> + </div> + </div> + </div> + + <!-- Qualification Levels Table --> + <div class="card shadow-sm table-container mb-3"> + <div class="card-body p-0"> + <div class="table-responsive"> + <table class="table table-hover align-middle mb-0" id="qualificationsTable"> + <thead class="table-light"> + <tr> + <th>Label</th> + <th class="text-end pe-4" th:if="${session != null and session.logintype == 'adm'}">Actions</th> + </tr> + </thead> + <tbody> + <tr th:each="ql : ${qualificationlevellist}" class="qualification-card"> + <td class="fw-semibold qualification-label" th:text="${ql.label}"></td> + <td class="text-end pe-4" th:if="${session != null and session.logintype == 'adm'}"> + <div class="d-flex gap-2 justify-content-end"> + <a th:href="@{/deletequalificationlevel/{id}(id=${ql.id})}" + class="btn btn-sm btn-outline-danger"> + <i class="bi bi-trash"></i> Delete + </a> + </div> + </td> + </tr> + </tbody> + </table> + + <!-- Empty State (initial) --> + <div th:if="${#lists.isEmpty(qualificationlevellist)}" class="empty-state text-center py-5"> + <i class="bi bi-award text-muted" style="font-size: 3rem;"></i> + <h4 class="mt-3 text-muted">No qualification levels defined</h4> + </div> + + <!-- Empty State (after filtering) --> + <div id="noResultsState" class="empty-state text-center py-5" style="display: none;"> + <i class="bi bi-search text-muted" style="font-size: 3rem;"></i> + <h4 class="mt-3 text-muted">No matching qualifications found</h4> + <p class="text-muted">Try adjusting your search</p> + </div> + </div> + </div> + </div> + + <!-- Pagination and Info --> + <div class="row"> + <div class="col-md-6"> + <div id="pageInfo" class="text-muted">Showing 1 to 10 of <span id="totalRecords">0</span> entries</div> + </div> + <div class="col-md-6"> + <nav aria-label="Page navigation" class="float-md-end"> + <ul class="pagination" id="pagination"> + <li class="page-item disabled" id="prevPage"> + <a class="page-link" href="#" aria-label="Previous"> + <span aria-hidden="true">«</span> + </a> + </li> + <!-- Pages will be inserted here by JavaScript --> + <li class="page-item" id="nextPage"> + <a class="page-link" href="#" aria-label="Next"> + <span aria-hidden="true">»</span> + </a> + </li> + </ul> + </nav> + </div> + </div> + </div> + + <script> + document.addEventListener('DOMContentLoaded', function() { + const searchInput = document.getElementById('liveSearchInput'); + const tableRows = document.querySelectorAll('#qualificationsTable tbody tr'); + const noResultsState = document.getElementById('noResultsState'); + const initialEmptyState = document.querySelector('.empty-state'); + const pageSizeSelect = document.getElementById('pageSizeSelect'); + const pagination = document.getElementById('pagination'); + const prevPage = document.getElementById('prevPage'); + const nextPage = document.getElementById('nextPage'); + const pageInfo = document.getElementById('pageInfo'); + const totalRecords = document.getElementById('totalRecords'); + + let currentPage = 1; + let pageSize = parseInt(pageSizeSelect.value); + let filteredRows = Array.from(tableRows); + + // Initialize + updateTable(); + updatePagination(); + + // Search functionality + searchInput.addEventListener('input', function() { + const searchTerm = this.value.toLowerCase(); + filteredRows = Array.from(tableRows).filter(row => { + const qualLabel = row.querySelector('.qualification-label').textContent.toLowerCase(); + return qualLabel.includes(searchTerm); + }); + + currentPage = 1; + updateTable(); + updatePagination(); + }); + + // Page size change + pageSizeSelect.addEventListener('change', function() { + pageSize = parseInt(this.value); + currentPage = 1; + updateTable(); + updatePagination(); + }); + + // Pagination click handlers + pagination.addEventListener('click', function(e) { + e.preventDefault(); + if (e.target.closest('.page-link')) { + const target = e.target.closest('.page-link'); + if (target.getAttribute('aria-label') === 'Previous') { + if (currentPage > 1) currentPage--; + } else if (target.getAttribute('aria-label') === 'Next') { + if (currentPage < Math.ceil(filteredRows.length / pageSize)) currentPage++; + } else { + currentPage = parseInt(target.textContent); + } + updateTable(); + updatePagination(); + } + }); + function updateTable() { + const start = (currentPage - 1) * pageSize; + const end = start + pageSize; + const visibleRows = filteredRows.slice(start, end); + + // Hide all rows first + tableRows.forEach(row => { + row.classList.add('hidden-row'); + }); + + // Show only visible rows + visibleRows.forEach(row => { + row.classList.remove('hidden-row'); + }); + + // Update empty states + if (filteredRows.length === 0) { + noResultsState.style.display = 'flex'; + if (initialEmptyState) initialEmptyState.style.display = 'none'; + } else { + noResultsState.style.display = 'none'; + if (initialEmptyState) initialEmptyState.style.display = 'none'; + } + } + + function updatePagination() { + const totalPages = Math.ceil(filteredRows.length / pageSize); + totalRecords.textContent = filteredRows.length; + + // Update page info + const start = (currentPage - 1) * pageSize + 1; + const end = Math.min(currentPage * pageSize, filteredRows.length); + pageInfo.textContent = `Showing ${start} to ${end} of ${filteredRows.length} entries`; + + // Clear existing page numbers + const pageNumbers = pagination.querySelectorAll('.page-number'); + pageNumbers.forEach(el => el.remove()); + + // Add new page numbers + const maxVisiblePages = 5; + let startPage, endPage; + + if (totalPages <= maxVisiblePages) { + startPage = 1; + endPage = totalPages; + } else { + const maxVisibleBeforeCurrent = Math.floor(maxVisiblePages / 2); + const maxVisibleAfterCurrent = Math.ceil(maxVisiblePages / 2) - 1; + + if (currentPage <= maxVisibleBeforeCurrent) { + startPage = 1; + endPage = maxVisiblePages; + } else if (currentPage + maxVisibleAfterCurrent >= totalPages) { + startPage = totalPages - maxVisiblePages + 1; + endPage = totalPages; + } else { + startPage = currentPage - maxVisibleBeforeCurrent; + endPage = currentPage + maxVisibleAfterCurrent; + } + } + + // Add page number items + for (let i = startPage; i <= endPage; i++) { + const pageItem = document.createElement('li'); + pageItem.className = `page-item page-number ${i === currentPage ? 'active' : ''}`; + pageItem.innerHTML = `<a class="page-link" href="#">${i}</a>`; + nextPage.parentNode.insertBefore(pageItem, nextPage); + } + + // Update prev/next buttons + prevPage.classList.toggle('disabled', currentPage === 1); + nextPage.classList.toggle('disabled', currentPage === totalPages || totalPages === 0); + } + }); + </script> + </body> +</section> </html> \ No newline at end of file diff --git a/src/main/resources/templates/sector/sectorList.html b/src/main/resources/templates/sector/sectorList.html index b30d471..cb3e68a 100644 --- a/src/main/resources/templates/sector/sectorList.html +++ b/src/main/resources/templates/sector/sectorList.html @@ -2,48 +2,307 @@ <html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> <title>Sectors</title> <section> - <header> - <h1>List of sectors</h1> - </header> - <article> - <p th:if="${#lists.size(sectorlist)} == 0">No sector defined yet.</p> - - <th:block th:if="${#lists.size(sectorlist)} > 0"> - - <table class="table table-striped"> - <caption>List of sectors</caption> - <thead> - <tr> - <th scope="col">#</th> - <th scope="col">Label</th> - </tr> - </thead> - <tbody> - <tr th:each="sec : ${sectorlist}"> - <th scope="row" th:text="${sec.id}" /> - <td th:text="${sec.label}" /> - <td th:if="${#ctx.session.uid} != null AND ${#ctx.session.logintype} == 'adm'" - class="nav-item active"> - <a th:href="@{/deletesector/{id}(id=${sec.id})}"> - <img th:src="@{img/minus.png}" alt="Delete this sector" class="minilogo" /> + <head> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"> + <style> + .sector-card { + transition: all 0.3s ease; + border-left: 4px solid transparent; + } + .sector-card:hover { + transform: translateY(-2px); + border-left-color: #00B8DEFF; + background-color: #f8f9fa; + } + .table-container { + border-radius: 8px; + overflow: hidden; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); + } + .empty-state { + min-height: 300px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + .search-container { + position: relative; + margin-bottom: 1.5rem; + } + .search-icon { + position: absolute; + left: 12px; + top: 10px; + color: #6c757d; + } + .search-input { + padding-left: 40px; + border-radius: 20px; + border: 1px solid #ced4da; + transition: all 0.3s; + } + .search-input:focus { + box-shadow: 0 0 0 0.25rem rgb(0, 184, 222); + border-color: #00B8DEFF; + + } + .hidden-row { + display: none; + } + .pagination .page-item.active .page-link { + background-color: #00B8DEFF; + border-color: #00B8DEFF; + color: #ffffff; + } + .pagination .page-link { + color: #00B8DEFF; + } + .page-size-selector { + width: auto; + display: inline-block; + } + .controls-row { + margin-bottom: 1.5rem; + } + </style> + </head> + <body class="bg-light"> + <div class="container py-4"> + <!-- Header Section --> + <div class="d-flex justify-content-between align-items-center mb-4"> + <h2 class="fw-bold text-black mb-0"> + <i class="bi bi-tags me-2"></i> Sectors + </h2> + </div> + + <!-- Search and Page Size Controls --> + <div class="row controls-row"> + <div class="col-md-6"> + <div class="search-container"> + <i class="bi bi-search search-icon"></i> + <input type="text" class="form-control search-input" id="liveSearchInput" + placeholder="Start typing to filter sectors..." autocomplete="off"> + </div> + </div> + <div class="col-md-6 text-md-end"> + <div class="d-inline-flex align-items-center"> + <span class="me-2">Show:</span> + <select class="form-select form-select-sm page-size-selector" id="pageSizeSelect"> + <option value="5">5</option> + <option value="10" selected>10</option> + <option value="20">20</option> + <option value="50">50</option> + <option value="100">100</option> + </select> + <span class="ms-2">entries</span> + </div> + </div> + </div> + + <!-- Sectors Table --> + <div class="card shadow-sm table-container mb-3"> + <div class="card-body p-0"> + <div class="table-responsive"> + <table class="table table-hover align-middle mb-0" id="sectorsTable"> + <thead class="table-light"> + <tr> + <th>Label</th> + <th class="text-end pe-4">Actions</th> + </tr> + </thead> + <tbody> + <tr th:each="sec : ${sectorlist}" class="sector-card"> + <td class="fw-semibold sector-label" th:text="${sec.label}"></td> + <td class="text-end pe-4"> + <div class="d-flex gap-2 justify-content-end"> + <a th:href="@{/deletesector/{id}(id=${sec.id})}" + class="btn btn-sm btn-outline-danger" + th:if="${session != null and session.logintype == 'adm'}"> + <i class="bi bi-trash"></i> Delete + </a> + </div> + </td> + </tr> + </tbody> + </table> + + <!-- Empty State (initial) --> + <div th:if="${#lists.isEmpty(sectorlist)}" class="empty-state text-center py-5"> + <i class="bi bi-tag text-muted" style="font-size: 3rem;"></i> + <h4 class="mt-3 text-muted">No sectors defined</h4> + </div> + + <!-- Empty State (after filtering) --> + <div id="noResultsState" class="empty-state text-center py-5" style="display: none;"> + <i class="bi bi-search text-muted" style="font-size: 3rem;"></i> + <h4 class="mt-3 text-muted">No matching sectors found</h4> + <p class="text-muted">Try adjusting your search</p> + </div> + </div> + </div> + </div> + + <!-- Pagination and Info --> + <div class="row"> + <div class="col-md-6"> + <div id="pageInfo" class="text-muted">Showing 1 to 10 of <span id="totalRecords">0</span> entries</div> + </div> + <div class="col-md-6"> + <nav aria-label="Page navigation" class="float-md-end"> + <ul class="pagination" id="pagination"> + <li class="page-item disabled" id="prevPage"> + <a class="page-link" href="#" aria-label="Previous"> + <span aria-hidden="true">«</span> </a> - </td> - </tr> - </tbody> - </table> - </th:block> - - <aside th:if="${#ctx.session.uid} != null AND ${#ctx.session.logintype} == 'adm'" class="row h-10"> - <form action="/addsector" method="get" class="col-xs-12 col-sm-6 col-md-4 col-lg-2"> - <fieldset> - <label for="labelsector">Label</label> - <input type="text" id="labelsector" name="labelsector" autofocus="autofocus" minlength="3" - required /> - </fieldset> - <input type="submit" value="Add" /> - </form> - </aside> - </article> -</section> + </li> + <!-- Pages will be inserted here by JavaScript --> + <li class="page-item" id="nextPage"> + <a class="page-link" href="#" aria-label="Next"> + <span aria-hidden="true">»</span> + </a> + </li> + </ul> + </nav> + </div> + </div> + </div> + + <script> + document.addEventListener('DOMContentLoaded', function() { + const searchInput = document.getElementById('liveSearchInput'); + const tableRows = document.querySelectorAll('#sectorsTable tbody tr'); + const noResultsState = document.getElementById('noResultsState'); + const initialEmptyState = document.querySelector('.empty-state'); + const pageSizeSelect = document.getElementById('pageSizeSelect'); + const pagination = document.getElementById('pagination'); + const prevPage = document.getElementById('prevPage'); + const nextPage = document.getElementById('nextPage'); + const pageInfo = document.getElementById('pageInfo'); + const totalRecords = document.getElementById('totalRecords'); + + let currentPage = 1; + let pageSize = parseInt(pageSizeSelect.value); + let filteredRows = Array.from(tableRows); + + // Initialize + updateTable(); + updatePagination(); + + // Search functionality + searchInput.addEventListener('input', function() { + const searchTerm = this.value.toLowerCase(); + filteredRows = Array.from(tableRows).filter(row => { + const sectorLabel = row.querySelector('.sector-label').textContent.toLowerCase(); + return sectorLabel.includes(searchTerm); + }); + + currentPage = 1; + updateTable(); + updatePagination(); + }); + + // Page size change + pageSizeSelect.addEventListener('change', function() { + pageSize = parseInt(this.value); + currentPage = 1; + updateTable(); + updatePagination(); + }); + // Pagination click handlers + pagination.addEventListener('click', function(e) { + e.preventDefault(); + if (e.target.closest('.page-link')) { + const target = e.target.closest('.page-link'); + if (target.getAttribute('aria-label') === 'Previous') { + if (currentPage > 1) currentPage--; + } else if (target.getAttribute('aria-label') === 'Next') { + if (currentPage < Math.ceil(filteredRows.length / pageSize)) currentPage++; + } else { + currentPage = parseInt(target.textContent); + } + updateTable(); + updatePagination(); + } + }); + + function updateTable() { + const start = (currentPage - 1) * pageSize; + const end = start + pageSize; + const visibleRows = filteredRows.slice(start, end); + + // Hide all rows first + tableRows.forEach(row => { + row.classList.add('hidden-row'); + }); + + // Show only visible rows + visibleRows.forEach(row => { + row.classList.remove('hidden-row'); + }); + + // Update empty states + if (filteredRows.length === 0) { + noResultsState.style.display = 'flex'; + if (initialEmptyState) initialEmptyState.style.display = 'none'; + } else { + noResultsState.style.display = 'none'; + if (initialEmptyState) initialEmptyState.style.display = 'none'; + } + } + + function updatePagination() { + const totalPages = Math.ceil(filteredRows.length / pageSize); + totalRecords.textContent = filteredRows.length; + + // Update page info + const start = (currentPage - 1) * pageSize + 1; + const end = Math.min(currentPage * pageSize, filteredRows.length); + pageInfo.textContent = `Showing ${start} to ${end} of ${filteredRows.length} entries`; + + // Clear existing page numbers + const pageNumbers = pagination.querySelectorAll('.page-number'); + pageNumbers.forEach(el => el.remove()); + + // Add new page numbers + const maxVisiblePages = 5; + let startPage, endPage; + + if (totalPages <= maxVisiblePages) { + startPage = 1; + endPage = totalPages; + } else { + const maxVisibleBeforeCurrent = Math.floor(maxVisiblePages / 2); + const maxVisibleAfterCurrent = Math.ceil(maxVisiblePages / 2) - 1; + + if (currentPage <= maxVisibleBeforeCurrent) { + startPage = 1; + endPage = maxVisiblePages; + } else if (currentPage + maxVisibleAfterCurrent >= totalPages) { + startPage = totalPages - maxVisiblePages + 1; + endPage = totalPages; + } else { + startPage = currentPage - maxVisibleBeforeCurrent; + endPage = currentPage + maxVisibleAfterCurrent; + } + } + + // Add page number items + for (let i = startPage; i <= endPage; i++) { + const pageItem = document.createElement('li'); + pageItem.className = `page-item page-number ${i === currentPage ? 'active' : ''}`; + pageItem.innerHTML = `<a class="page-link" href="#">${i}</a>`; + nextPage.parentNode.insertBefore(pageItem, nextPage); + } + + // Update prev/next buttons + prevPage.classList.toggle('disabled', currentPage === 1); + nextPage.classList.toggle('disabled', currentPage === totalPages || totalPages === 0); + } + }); + </script> + </body> +</section> </html> \ No newline at end of file diff --git a/target/classes/application.properties b/target/classes/application.properties index a5b3aed..c970441 100644 --- a/target/classes/application.properties +++ b/target/classes/application.properties @@ -18,7 +18,7 @@ spring.jpa.show-sql=true logging.level.root=info logging.level.org.springframework.web=debug spring.jackson.serialization.indent_output=true - +spring.thymeleaf.expose-authentication-attributes=true # Session timeout in seconds server.servlet.session.timeout=10m @@ -29,4 +29,11 @@ jobmngt.admin=007 spring.thymeleaf.cache=false -spring.jackson.serialization.FAIL_ON_EMPTY_BEANS=false \ No newline at end of file +spring.jackson.serialization.FAIL_ON_EMPTY_BEANS=false + +temp.upload.dir=./temp_uploads + +# Taille max des fichiers +spring.servlet.multipart.max-file-size=5MB +spring.servlet.multipart.max-request-size=5MB +spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/config/AppUserDetailsService.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/config/AppUserDetailsService.class new file mode 100644 index 0000000000000000000000000000000000000000..ad6039299fc3de4be3ddced5a871d4a260cb45d2 GIT binary patch literal 3776 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 OAjvR;VHU$221x+6lxwU2 literal 0 HcmV?d00001 diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/config/CheckAuth.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/config/CheckAuth.class new file mode 100644 index 0000000000000000000000000000000000000000..cac60139015c2d5e855b6b68d64f0e83b948b1de GIT binary patch literal 1052 zcmX^0Z`VEs1_oOOO)dr|24;2!79Ivx1~x_pfvm)`ME#t^ymWp4q^#8B5=I6#o6Nk- z5<5l)W)00S4h9ZJ2A-V!^z_se&%Ds$)FN&ME(UIP1|A*;UIso!28FD|?8KsyME&B_ zqOzRS68(&lk^+5?l9Gbp)Z*gI{5(bmp7hib$C8qw%%swiR7M6h4IhN1!6ikRdFj@g zNJ0>IShF(-Ff!<;73n9I<Rs>mWEPgD>SyMb=x64o85tVtXXPj5=B1bDr{<MpmQ?CH z78HOS$Hl<UAjHEU%pk(Zz?z;~;+X>Wh9^6N7$burLBo>s^U^ZY^_??Pld~O5OEMT4 zSe)}yQW+WeeKPY>{YrC_Qj0<olX6lS8H9ZDlM{2o5{oiHe5fExNk(QdBZILI$u?Ot zG6-iDgS-H;7v!nr#FEq$Mg~<-XyFfGYt1M|2KHiDa4Y!`iV|x^2HxP3#N=$>!~&?x z1$<LWGV)Ub5{nXZQ%h2dilOmDz-CX7)l8Wwj0}vPj0`NL#i>P%42Fcg5Aq-))~p#B zxPtRbi;`2_GC^U>2af<fP)cD?WDsRwVgLbV21Zc2Wng3wXJBB^XJBMtWME)m)!NR$ zxRHT@fr&wafq{VyEXc+n$-uzC17@=_NHIt=Ffed3$S}w<Fo89DF))GUM754F2x$qi z?_}U)VmQXY&a#|=g?SAF0|PUXVH`+?i89DB$TKi7D1dBcU=ns<WKaY#85!iDRwzNO z=mF(51_lO0tz!%_2rC3(%mWPk%NbZ0eRYm8@at@25R_hkVLcxMGuRjW47?2T418c` zD1gNbq0V4nFl10>P+?$XkY^BLP-ReKU;;ahpMgczft`W>4}$<Z13%P}oJ?RxDl;%J LFfyovLrenz&o=Wb literal 0 HcmV?d00001 diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/config/DataInitializer.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/config/DataInitializer.class new file mode 100644 index 0000000000000000000000000000000000000000..e2f8cc63a47c4310690bf473a1b2c7cc67b4995f GIT binary patch literal 3887 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 F1OPplF)aW9 literal 0 HcmV?d00001 diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/config/SecurityConfig.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/config/SecurityConfig.class new file mode 100644 index 0000000000000000000000000000000000000000..95ed94c03ea58bc843bb2dc181c01624cb8f19cb GIT binary patch literal 3634 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 HFh~Lbktb;i literal 0 HcmV?d00001 diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/controller/ApplicationController.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/controller/ApplicationController.class index 3fcaf5ff35a551475b6e68047acb99678d811296..4cc1994c9a506541ebfb1c18b899fa6077dc2467 100644 GIT binary patch literal 14037 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 PVqj#v&UlmYHiIMp{#5iZ literal 11829 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≫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 FNdSs3zMlX9 diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/controller/CandidateController.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/controller/CandidateController.class index c1d26ac598126b95b48b57bfb0a05a5f924f14ca..17db613fec568b92ab5c0ff7f772e05775016ea9 100644 GIT binary patch literal 7288 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 M;UmLmhOZ2g0JYH!DgXcg delta 2709 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 diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/controller/CompanyController.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/controller/CompanyController.class index 2d63e8afadfea3eb2a4cc75e3f6f7f00c4a55ad9..87cbd216691b76f5806fe8469d6c459b9105acb6 100644 GIT binary patch literal 9948 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 literal 7997 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 Ik)1&j0OvN&9RL6T diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/controller/JobOfferController.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/controller/JobOfferController.class index 811cc81375a5b878017bc133bf637de270a86cb0..504c1def4d4fe98ad2eff56c8f9520b32169633e 100644 GIT binary patch literal 11695 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 literal 11006 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 diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/controller/LoginController.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/controller/LoginController.class index 90153c1ab690d1c2889ce5dbcc4d948667b050bc..0d97931f30745e3b35e374a3d83aecec418fa65b 100644 GIT binary patch literal 5267 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 G3=9CKR0vuC literal 4092 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 QTnvm1D;ZWZtYwe{05{cy_5c6? diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/controller/SectorController.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/controller/SectorController.class index 891e652c6835417fc6eb01c4fa8296edae1ee9ac..6fb5cf1720deddfa5246f5ce24d64e2e0eb61775 100644 GIT binary patch delta 37 scmX@Yb&6}lVnzuG4Nae{#Ii*F(vr*^edqj~oYdr!%>2B~TN(SA0s5c~i2wiq delta 31 mcmX@bb%blfVn!h$4Nae{#Ii*F(vr*^eV@$YlFhpr`<Vf*whI#g diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/converter/CompanyConverter.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/converter/CompanyConverter.class index 69b5b745c27680d537d723367dd1d26780b226b5..0602d9553e6903813a9589831d9aeb7caba404e3 100644 GIT binary patch delta 493 zcmaFKvz~|R)W2Q(7#J9A8EiLlu`#*|W+j#->gOcprR#g<m87Pp7BMn#6eJcEr+Vg< zFfxd0_#hMnmlS2@rCV!yax+LVNV7A@@G!_S$T2dQPp)THoIH<FSyf!aQxl@0v?Mb} z-@l+FGe0je$C`^lfkBa-L5YV!nL%apS4K}pwaNBOW^5Wf44MpDldG9R7<D#ZV6tbb z*Jfa35MtnF;9+23U}j)sU}ex{U}Df?U|?`#U}9imU|`_V+RnhJrM;DbeIo+{0~3Qj z0|Nse12dRpVc=)rWDsC5U|?VngQ#XOWH4d?0WPqqj10yM3=FJ{4BQM142&S#p~h!` zjb~<HP?|iERkVI5gCr9J<2DBQ0}QGgz-~d<Bf`MVAj-hYAdX~@65Jjo1``HT21W)m z1_lNe1}1K}jV25X0*(xt3@T6?jlniDFmP&VA7J3y#-I+e2jU+#M+Qa)1_n;B1w2qT i#3NAWn+Sly#~d6277UhP+KR!Nfq{XIfsw(6fdK%<B|vBZ delta 540 zcmZ3_^OA?_)W2Q(7#J9A85}oqu`$Z=WhIs+>gOcprR)3T=cO|;a26yM6{kYjqLTxe zM6A8I7(^JPco?J^WEdIP%Mx=+Q~lEz8H6>wG<{HYTXQqWG03wsDDW^SGAJ=Jm`~Pc zQJlC-Y;q4HuZfxlx@xHQ(vr*^egA@z%>2B>9BVEH6$Vvy1~ncAbq0;e`xrf0v=|xm zC+oB5b8#_nFzE0w=rZU{c3}!(G}t_o$(~7EpMjA<h=H4dhk=2CnSqgkmBEmKiNR>{ zdlu<>V+IBWJ_cqm$-=<Tz{w!MV8Xz_AjZJNz`(%8V9H>|00LZK{frFe3=9mcj11fi z3=E7Qdq4&;Ffe2@FoQLyY9C|Z(%#0vJClKxaf8+t26pWo48l7YBzH2%GBGf2V^BW8 zpt%9;4zSJjV5>zKxEVwlcp1cztX73vt;%4*V9CJ9V8y_|z{0@94R?eE1A~AggD!&x z)DgxEOkhWFYH1%};M>Na4YG)d0b~&y10w?i11H#g9w-~+H7y31k1Pa0fn&|Uz`(>{ T!(a=h?HKGC7#Ki$9T*q@RYOgp diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/dao/AppUserDao.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/dao/AppUserDao.class index 72371914d226adbddc19b8e87dfb2415e2f364e5..85698403b3217618b238cfa5dc8b43f6e454ea23 100644 GIT binary patch delta 1889 zcmeyXKf{3Q)W2Q(7#J9A89r|0n$4om$>7Nl&chJF5Xs2E5)v8U%E%z-la*MOsGpOV zm#*)elUQ7A&CQ^|5Y597!w@_9FiQkqJVOFILn04D5<@a0gWP0ZcDc#!teS>g44w?B zJPc_J>5L2<nRz8)i8-aIj10^gnw}gCnT!m~iN(oW4A~4h><qa)40#OsliOGwqzf4t zm>q+iIT(r=8CYCggPnyK7#T`=7~B}jCO>4g<%nmf;9;m_sG6+Crp{P1*^kXbxQ>y5 zH77qkGmneGfuVtip^>3!@=7*kj(CO^9)?ziw#ir6qI5bK8JG*oQ$R|(co@1Fniv_l z(^E?V5{rw=^NUir8G0G|*ctkH7$z`GoE*ij%srW53OmD89)@WQ(<gVZTX6I;%;aI1 z#V~vF33dgJ$qaLO80Im|pZtN{h-D!ogU;kYHjT-C9BiVC85vk|6EkzT7?v_DV`o^- z!?1#(X>ulq0^2GchSdyfHurNpVcOimy@#3a5f{T_h9~R{PeHzWHaUp@2*(RX24(lu zl9I^+%p!aW<(VZJ3YjTZ3XB{KuO>SPSTVkxTrHr*@*Wh-b^LOZj|-@TtYu(eU}Iop zSjWJ?z{SAGzzXKAXJBB^XJBMtWME)m)!NR$xRHT@fr()QR51gCAOlE*2cm#s6T@Z( z1_nk3Wri&b%na)%+Y9<IZkXI7s2&VbrNF?<pu)h%u$5sO0}}%SgE3S+h_#(z2LmI+ zP6h@Bb_S+ob_R7mhFu^#85y=SFfhOzw;OD_Db#c}A&}|8LWE7<gVXdq7^d4X>;=31 ztdJw)hROWG(m^n{OCh;k1MYSWhJA3;*%%lj*%{PfP6y=(RHyG}H~==hM%b$!6o_IB ztl(V3&tT7>#Nfc7%;3nN&)@_OR8Vj!BUxz(x6+W|Aj2VOaIrBkCLvjQ5b7Up21bV6 z42%qiqZp1bFftrvNM~ST0L5kq12Z`AW$J}Oe6+SONNaCm2;0dJ#mumiA&!yZ0E6c? zhLkn%SmpvN_F!OU@B}+b49u5-I)#}*lHnM`aRx?k458V1g5e|s3)s%-49pBH3=9mK z+B!lR+ZeKDGH?hL)GudX6)M`rP{P3A2Xcq5kOR~i<&5A2#tbGQ4)9|TXYdC*Knr3L z)By|(nhdAF4gj%EGaP4NVi0E#0y_%iBL)T;1}1-YhV1_gj10`|3>@qX+3XD23{30{ z^<V}lk#RCHoMK>LIL*Moa0cx0v)H_Ij^RAiJFgj-!O<2dRP76LxenCje!4=n+S?fF zcQQ0HGlc63wQFx<=!9~1GL$niSg}a5>}2R+WSGgoxkHL!(sBktNfzdv3^SM*4lp?E zV3-3+5-bcGCf^X1uLq?%6No)<&jvDF0H-x(20ySDnHU5Z92qV$Tw-8mFk+BrxXf^c zfra5J0|Nsno*;79;Bwa)Sim_<h=J)JgA+I;;@KH`ec2f%!&2-8co@{*z!nHM8E!!X z;W{)B9EBF32f`w4oSw$*XRw<=nIWEmnIVBepq?R-L5d-nL5U#+9Pf~zaD)eiBg0~F zyfZV{!GppO8Wc)cf&wH53JQoEN>CVK3yOM%#qhAWjTsai40qrKqzbt7V_@JB;@HNp zWCz2_%?#|I*n=cvP@&4eAk4tRa2K4o?tu*hiQZ>mV0gg5z`)1A#PE=Tk>NR*e97<{ RR0M;{;5Q8K7(Osa0sv`-Y>EH? delta 1864 zcmbPX@K>Mf)W2Q(7#J9A8QyH<n$04|&JfPXz?YR+mZ+bTn3t~alb@H)&7i;#$-@xE z5Iy+{O9WpmLmWFpJP$(xLn0%C++=Q6xycc%nqnLb$&3uliN(oW45<uh><sBV3>gfW zlQ*$CNM|!LFgpf2b1>vGGO)O~20IHeFf!!xFt{-kOy*^?<%ne{;$bLeD486@rp{P4 zxr)t0xPp;^H77qkGmneGfuV|rp_-v)@>Movj#!2|9)@~`hDq#EI!%lW%mw8sAQde< z46O_`j11iAsU-o4#l_|MMJe12?F=3444pg-T@2lm+t`)4dl~xJ8Txq`CNNB#yoKF@ zqn%+g55p9Osgu93D{%BOOy^;k!7y{O42Kcp?8*KdhN5#B8CY@?Gjq5Y<})l{XIRL? zu!x~%@>C85wk13aOBt4J-p%oZNmxij(+4%ctT)f)-p9;$my6*Z!+my!2Oyt5oSeyj zgz@oYJpm1tr;H40lWW-|CKn0Fv%FwrP@Q~0K#ohrCqF;Cw1A!A)#SYbt|7}A7#P?X z7#UVDFfed2Ffy=$c`F$h81xw!85kKD7+AHoGcaysU|?WkSOrzgz#z!5nt_3V2cm#s z4Z~Un1_nk3WrlSO%nU0hhY9*HuA019P(2u=N`ZlyL4|>jVLihJ1||ju24kpt5NjjD zCI&`^%?u0-><moF><sFB3|l~UGBSW{ggI_2*mP5<>9Rr~)3byKo4yUF>Dw3>7*I{O zW7rOM`x7C@dXPWG7+4t?7>pRW8B7=?8B7_Z7|a;d7|g-`2Kh(|$wwM+A89b`fZNH& zz!=HSpbqoU4h$=IGVB6b$-uxcnSq6YnSp`9a1#UjCWgsNge@6YO}-~AT@MRCWhC1S z;kFqv>}J>l4L>#p#v~-$c0+CBW?*F4%D~95H;Q2&10%zJh64;t44~)@U|<HvhJ;Xv zkJc6jX>FmfoeUAo3_BTO7#R*Qglz!Dcs)4ExxflN7?>G6!QK}G^Ch6pU}g|yILL5_ zfe{>_X!aduIKsdJwr@HEGXo0)1B0fvj!@DzhLo8M970*k8CZpKb}{5JF!+Jopey76 zbweQ|1IPjO%t#LKV-RQX2RlFuViME=3=EnKN5KvNv5qkuVqjtrXAlBAiW!`?WEhzI z*%?y*GcYnRvomn8Go-RJq%tr;X;6aWWMVkVz`$^ffq~&T$m0wrz`m(R^Ug_zQ&8`` zW?%*<kU*hQUy##vpicMG6)M->#!$JFp_Z8;Tvw=3dmBSDl(UnekdeWPMUrJFLmMN* zOa{&!3_Z&k1SMIRcQQ<3VmQFyu!CV5$lEMnUrRBt)iW?KgfK8Ogfa*)gfYl6gfnO{ zL^2pLM1h0B1mXgCFa$E31}8pd20yUpnHU5Z92w3qoMm8UFk+BrILC0Ffra4$0|Nsn z1|f16;c}N4SipfL#K82A!3i8TvGwc>?Y`^`y|6TV8Xi8E!Qq1zJXaX5LWAczG<Y0^ zW}yeq9Btx*gA*Pelf)sJ0pe#zxSt&v=7OW1nZXY3XG5r;m9Y33BnR>{L=MHzMi4)v zd3i3}%XQZ<{mj8|9bRCnfJ;LL1|A`fZ4C2vFf888zz!<K7{Rd#Dr6ZLgc(>EZh#Zm qO|W4g(OV1*47V8=82A{N8167IGCTs4PZ*v-i{$4FFBx7lNCE)*YhjcC diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/dao/ApplicationDao.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/dao/ApplicationDao.class index 8387ebab7a1575d0e80c1445458e1423f979e1da..156940b0ccbc02225287ba7f8611daebc3acfe9c 100644 GIT binary patch delta 1959 zcmew;`A3!O)W2Q(7#J9A8CGrNddA|w!4S&GpzW4glAMv5m#&alP>_?EoLG{XpI5Aq zmS3choS2uAnUYwNs^IBjrBH29%f%qZ5YEFO$q>QF;Gp3%S&>nAayX;-<ZKr1$-fx6 zS)v#j!Y6aF$kc~>xCXf@B<jIU(929wuvM^v^F31({DWLvgA|-16%zFl3kty2xVQ#8 zb1=j(GH@d)<Yw?^h~r_1XGmaV;3-Zm2}mqT%uOvxEn;MF)9}ekEKAhSNz6;v4=yRn z%uBZhiA>ICVV``GnS1gfMxpwU%7WCCz|z#BN^1^=Bu0j`U{@bk=MV*F|4_dWjYLfa zw;+FC1xJME6%rMQalfOVi$bDaR(_IyT3Tum)PpdtX9_z*3L^twR$@KOeLnel=^PAc zj0{`|MIgUr@GxXDWHB=Er>B+#XXd5nqz0uHm*$i(GKgqsf_ww@pMO$TYI2D+7eg{b z4i7^vLmndoXHI@zdRSskX(}TFvxcS@7efI<Av;464?{6SFe8H~C~&m&OG`3y^quo_ za#EAQam&cSnVMTrQt6XfT*Adr%23A6P|m|p!B9E*FN--_H4j4#L+xfO)}PF}f@zs~ zDNdEnuyo*=!pI=3;R$sW*j$hy){G2Z8lID1a(E@nq*i1WmlPxEam-8cf`$<zgC=r> zdFGX*rl%HxV+`s?n2=@^BZDxK-(lj63`Rba4={=#MXC*wGEfj(+gUR*2tXVTvl!%V zRcuyc@ktCZ>L&l>STA13z`(%9z{pU~z`(%8z{tP~<~2;-&nc$f2$f@C5N2p%U|`^3 zU}RumU}b1#XklPrU}R8cXk}n#XqfzmQ#}BrmXCp%fq_AWftx{=L6kubtVfK2iGhJZ z45|mjYGY_;U}We3nZ(G@#=yV;b3!NBP(!Gp#aynYAVY;1SQr=>lo+@flo|LLR2YmI z)Q}7|fg5bX&;>TQd-7W@c?GEPT^PpeGW3Amti!G412Ucq>^?09ZU${6V<q9nN;33< zjfEQ6i(z0NLq7uxL&M}n-0Gnq13`hwz+ix67_#RlFib>vegXpn!z2a<1}28d;6UJJ zU}WfIU}TsQ#W0nDkzpD`FxaVbJcjiklOe%t%D~NF#vsXHj%0%Z)VY!j><rTxW-u@^ z%!Ec4L}V5NBh;eV*esgEFc;(n1_p*+1{QD}8g61>-^9Qx6e7KY!E75tSiRO325F&4 zA(Nd9Ud#->IzrJPiJc6w%nac=I~n{K88$F5FfcQKoC%3AYX*J>8wObhdj>5A2e4BW zASOed%D`aA5CaYjSq2fXqnY52hKS5!V1lKR7*Ohjgw#B29+=Ot02ERT3=HcTSitGU z671kap=7Xw{d9$*wIQz66-w2CvUW10Gcp`tNZ!Vf%{gTr13Sxd1{UTu3=9k`Nd9nP z;A3!SsFz^yU{GT41bajaVguYGmJBfr3&B~#k|B*@Ap;WwAA>g7OU!UDK|~fYFf(+6 zGaAz$26J`>e+NiL;ACQmVPIfLgC;Jtbg-CVNj)@vEEpKU>6K*%Lp~_PnZPcEB)MR) z;~@Es1@1T&u#+Ixurh+JfCS`H%z)%zSjGS{tAT-mK?Up~1_mA>j%^Gjx;q%EHZ!nq d1m{ptB!iSOFo-fRF)Rls;1yugK%y%d7ywmLmjeI* delta 694 zcmeyP`cabW)W2Q(7#J9A85%ZnJ!9eJVhCjjV`m8GVTfReoE*(=&KAwX5W^6=c?SDW z=E)Ykr6F+)3=C`xj12J%3=HfHj0~(`UIGIHgFXWz10w?i1FP0{2F8sH3=B*RiBQE1 z41x?v3=9lB5Csg$3@Ho@42%rQ45<vv3<;AN`P2hIYWWzL85kI(7`Pdv8AKUmz<R_O zm>3us#GraWtTcvn21bSqkX4KfX$%YuFgIm_4K;)sTEXX94>DAUfrWvAL4kprL6L!< zL5abbK?TWR6S%=93|U}<v%yYaPGM(I_|L$}z{JkL!Oma;q7~Q~m{?#IXJJ^Z%a8-| z4Fdy1AOkbl)st=b<$XYI-~w|s7`PcUk?fX)+bzkE3$`2Tsay=R^BD3OSQru}FXvYe z1sMp60tN;>B*TzHsDPmm5kdtF3=Bo!=qLt<6gLASLnZ?wLrD}vDFY)z8ACA0)svM4 z48g$#i34K>ZUz$uNd{9S8x)|<m1JONC}*f(U}UI-#wkRkih&VoQ8lJT91Jz^l%fJo q1`G^5LLA!|LUu4jZDwExB^YH!q!c5}z{F4sc6J@uG?0($85jT#Hc68J diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/dao/CandidateDao.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/dao/CandidateDao.class index f18ce38b42f4a6f07d91ac4c0a5f675eb28d12d6..c9d4923bc1613f83c9280539017f6a48a2151098 100644 GIT binary patch delta 1323 zcmdlf@n3@L)W2Q(7#J9A8S*D`=`(Uov@)2)CLqViz?z;~;+ew8z^tL^$;rUSz|YPg zz{4QOAjHUEIQc%S;^coU0u!q&xJ4L5*%`!m7{nPQChqm%5MhwwVUT8!nXJHQsKCu2 z!XU@PAkQGd$iP;Vnwwvi%Eh3_pv2Cg%)_9<pgMUfqa}w3gE|j`27~70YmCl}+LNW2 zj5$OYba@!`81yHHGDS!jGBU8_CT8YvGZ-_NurrwQFqkozGcw3cJRmZ80Tb8c$4pTi z#tfD`3|0)*lU<pO_-q;M*ct437#tWJ85zVU$1;mgp2Mup!N=gt!{EZ;I{7rS8;d(5 zgXCmcCP^-ksh&IxUJTxoEm&@{vJ@wlrB1%ZnmgHx)rpTcxFj(-+c&WwBrz!`b#fPL z&}2bY%gyd=O^lOovM;IUV_;%nU|?im0)-0$BZChE0|OfaBZDsk0|N&GBLgd#=f}Xn zpwGa_z{tSBz^b*KfpH@P0|OI-KU6UT13yCm0|NsOL;*t}LlD@|V1^I|W(JVvECwd9 zMT*+n7}#eq@M~>h;MLyAAY9MHpuGXgkz`^xz`(JAfq{V;Y_bpo3j+g#7y~<lID;UA z1cNYx6xbFqh)Gae7#I{8LK(ss7#YIBIvIr-B0#QWWC&$ofO{tr>>hcBD5!g!q3)^Y zhFUJm#DMS<7uW^`22f0c`~|U*8*U>v)JB{ZNy9zv1ht3{Y7fGb;NYtV2QLGIDw5Cm z;P&tlvd4wN73v{(24=9Mg+cZRaY*lAP}s?!#taKluw_uYw2<to7lvCV%n;2G!@vj& zy=Z9YaWgP7L^3ck#6~g1F)%X3GbAvufTOgAfti7sfq_8<Y@?PC$4&+vW(Hp!Ap`B5 z3`Wch;W|4REEpMfGT1OOAR?6ytVf@LkHL^Zg2AXB>=p&EpbFe6Dh!4Ui42Skd<<d? zNesyhObjW|)CCbqWnh9i(~yCIAq|`u)4`FA=G+X1OlSb*GBAN{5!I63#^B_ubBuvk zYYRg?ug*>eXxt;GI&%g#1`7r*21^DJ25TfYio)F}3Joqc27YiXF@e(`2Lt0D1`&1! z4w(C3!3fGzkVKfp;0<+>Edz5FILAOD%?%oaI~hD6A%+}j_FzXqG8in*ApXwA^fwbj P4me11!BQZr^B5Qa9dw-x delta 1237 zcmew_u~UNU)W2Q(7#J9A8L}pF=`(Upv@)2u(}SCjfuEg0fQLblL1;1$qlYvfg9s0U zD1#6q16xsQZhl!RHv=DoI1hsagXH8GMng7s25CkH!^sY8hP+%1vJ7(U4Dvh-3Ji*q z*D_ji@G&U!FsLx7PJYMe%%aZ7pf_2UMRKwqlQ;(-gC-Ay7K8TWT&4(7T}B3$+{DZr zZU%h@19k>O9tI-@<H@&}syXx-OnDg07|bWfG8=JQGFY)QSo1L0FxXC>#q7wz!C=qB z;K1NG`7yH_qw{1f7C8=m23H;iHwO2~0W3Ee%_bYNnlgq>p2_Mm`7LX}WCPah&1Gy& zjC{PoC5g$|zKI1PiAg!BlfSVqtLI=~VqjokWME?8X5e99Wbj~MU|?flWbkBQVBlb2 zWMBpJycif5^cff#7#SECShcn@Fm7aEU|?eKhAL)Y;A8M%U;rDyz`)Aj%isq#RFJ`+ zftdlM*_nX}Y!SEC76x|hoeaE83}7Sb!DeuQ<wO~{7{tKlh=KXsP;(d<xETT%0vQ+? zf<V?YG6XO%z}*=Pwn!AGMZ!>vknE9S;9`)5+EWkmJqZ>`FoZyT<OKB*AJj);ObiDY zq&A?1i#*gGkTra8|M0>6Q_sM_ip@g~431EH+!>g`p&<;iM@UL~2ZPK`1|?=}mZ>0F zCJeVsm?4xQ3=w#t(7@wnU}OkpsApsdk79^mU}T77h+<#?$5jmjGXpaN1A_|KMl~U+ zoeUbx48A%-I@&uK^q3jKb#^kCFf#09uwY~W`Ii|SwtQecnhbml+6)p5I$)<LfW=hc zPElddWr${AWZ+{EV~AmhWnf~ckAo%~h)_HO6D*Q-85kH6z@eE4j%<i~;m%EBNQMSb zE&~(T8c{9jZ47q4I>#7zK@Qj1$pDRei2H;XSQr=>3>nxMj2O5Wj2T22Oc{I_>KPay zjueGEQWP3qYz+M1XkubWf#iih3?l3dQZV<y!V#2NAc-)Q0TfrD)Naeb49+T$ICFxA s;7$e?NPr<_9A^ega7cl&9s>g`(jfj$!}K>3LpnG}GQd(Gt1}rG0FO3`6951J diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/dao/CompanyDao.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/dao/CompanyDao.class index f4523efaf9abc0da87905cd33c5719743f932207..b4336a90fda6f25e7fd7b52f0fa7dac8675427eb 100644 GIT binary patch delta 1327 zcmbQK_+5eP)W2Q(7#J9A8CGrN@@MknV$fpHW@pghVbEpJV`LD_N-Rs%&q>Tn*Z0gT zNli~JVq{=1OUx-v^-p7D5YzC~^ubVJ&CMXfV8Fv*$Y3-%naP>kgu#@Z!HkE&oWWx9 zMy5b9D;@@G1{+2Oj<n3Y6sJnh6h;Pd4bRE@SwuyJ7#JArco^&%92gmx(^E?r85A@; zCnqo~aF{SS@h~_uxJ>R~b`x^rVQ^=VU}Rt`O3lqLOXXnjocw^<P{^B+fh9LFGl!c& zg~6AH!H>azvNB5)cOXL$J3}xJLkL6Y<PH{P4ikoO9)<{p$jO^olsN(!qCxh>Ouok= zC1Ao3$HNfMV8O`1l%K}VkT{uxRgo>3harU_b+SIIjIt=Ie}hYkGV{`{xfl`{(s>wI z88R3dL?;V!ifo?9YQ)6O=*h_7rQtdG1-lnBi-zZB7Y-XnW_b<I$${Lmlh<=5^YwEv zOkkME&M=9GVKT#%%?eyBjI7fb8ALQDPh=7n<Y!};!NtJGFq4O&gP~J^VfJKSZWX?{ zTnzIV=Cd;_06A*m<a+KSjEg6`@C30eV`NaBJdsO$ay}Oy8#}`aMg}=3mwobM9+{9# z1_lN;21bS~1_lOp21W)}FfW^dfkB^vk%5tcfq_+PI|JiJ1_lNuh8(D31_nNcTm}XP z9*6>lJcfJ*1_nlk0)|2cW`^v^!MsMXAhkjaEDQ_`q73W|Vhn-|;tawJl3;ye3``6R z42n>FAXX7WF#{t*2?GNIGXtYALn+8!Mus8=1_qb|%fL32GiZQqc*Ltx53&K|bOr`_ zu&EH&2t!S+0K2#nte=~Kk)e!%k)bMzp_+jal*$-b7(jmXVqgY4MNDf81FsN=^bQ8i zoecWS3<nrEb}$%k02_g@Mg?pQ#PMPb^$=e(Fo-eKFw`<I!W>=0z`y{pybhb??hN%H zA22X5Br`BEFf%YP$ZAP%W3ZIo#bC?8u#Lg-00W0F$T>PY8C;nd5CP8zR;<M!#Gu0< z!=TGh4~hW=u%Il|&kPK*P`3y%a5I3ylnL2Ys6pAl&<G8MWem(<V=O?{3V8@|>|~H< zW)Sky-pSy@%&?O|nUTR)XD34dBg0Gv&K(S4%NYc8n0E>?L@_}EDi#z_EDSl5C-RFU zxyl0WDhmd0h9(9^25AOahGvEq1}279XzGE8v@tM&<DQ3s=?{YzJ42ukJA(<#<=*h{ zY{v*s0WMG`U|<ACrUfYNfRYT~Zia+Nsa*_73=G>D(m;+wPP*FQScD`NK5%d`Ft9Lm zf#bRxYy&7!^)N6n^fE9o2r)1*^f53qOl6qPFpFUh10%yCh9wLP3|tJ13`-f7GpuBg F1OQ-1(+vOs delta 1367 zcmeyaFjJB1)W2Q(7#J9A8J2A1@@L}WW)NY};$hHc(3!lP$(dV^L7$z$fQP}5!D#YV zra&bV9tKkeGe!oEw9LE|r%KNhMg}zvpRB~PME#t^ymWn^{K)|v5~@NB3=9@L43-R5 zj10`_sU?gI`lzb%(ycWoA7GZ{;$qNau;F2_Ww4vPj@eDrfrr77L4uKittd4&zbuu5 z!I_akXL3KI_+$kZaUoYm2A15!%p7h86$W=61`h_$$(}4x+};d6><qp<41Ns$lh?5* zbLcSy@-PH31W&%nqHN^N5DIcg7$bwIhNmWyQ~i^&Qj<%pK@N!EVTfcfVq{>-Ph)3@ zo~*~J7#PdL5XTVD$RM7Zn4RjJpO>6i5}sL-;hdjWT#}eqQq0Jpg6Sh9(}PQjGV{`{ zxfr4t5_uR{8Im?nWc6fH5<pVolb@Gv&BzdmYR=>U4)Muf+0BH6QN{DptvBm&*f5Id zVTfz`Og3bdn%v2m!q>^g(8bWr&d|fd(96)b`8OvEBjbcg+)^TwxES~tCi5^fF*FM> zOl4${(}3s_vgT)Fn9jv8gJC8+!z_@eXHSmeKEgP6vOG@^%K}CQ)yWHZ<hWFP^7FGx z3)mSJO`gkRE1t~2z`(}9$dJOoz`)MH$iNEbrB43HBc`4Pm1AJwV@PLUVBle3WME)m zWyoO2WME)mWXNL3W?*JWovg=e#F#cYkyqRmq)Lo|iGhJZ5vmHr%3;W5U}VT+U|?Wo zU=(J^2N}Z1ki)>h0JFUSY+fOQ2H3p4yejnwyXBGW7KWNy#K6G7#83>@&&|NdP{6>* zP!h#Z%D@Op)C?>PsSFGZ?hMRerwD6pVc-?w(B8?Q$;@zofnx`Q?gp@agdHkiJ0LCx z+fmN|0>TVs4CM@rFc+6GFfc%DuE1uqBSR&~`CxA`F)%YQFvx0YZ(}gl-o;?fz_5+M z`TzrmFUT!AI~nYm7&gM)zz0^W#UR9>!yv<;3vx?6g92Dk7V2RJ23e?Egc!IPKmp2x z>?+i7tYWB!2Ej50X0S09AZvx3ggACG$TKqtxoGcXaARiJ$)L>0;H$Hf!Hbb$CIjaV zhJfV^0y@k)8A6!&AORH)3Mdwaw8{DW;`N}&g}BNB?kWogSB4q}Mh0mHS%zAMItC^N zP{@H23PhxVfe9S(JPb^K7_`_KynWai^k6P`g@<P&MtBNvff6_aBRD24KnVvF@_f4) zq9V0+F~l%1Y-dOSIgW_|5sTWOkb|aDK5%d`Ft9MRfWx^JYy&8zw=pm<v@<X;2r)1* lbTBY8^fOFkn8GlPfstVj!#oBC1}+9hhWQK&85T200s#7x@In9p diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/dao/JobOfferDao.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/dao/JobOfferDao.class index a925598f027de2690213cf12ef27ff30629aba44..d3684d1d03653fefd23d4e4029648fd78c1fa2e9 100644 GIT binary patch delta 84 zcmdnZy_kE$Rwi*V4Npy<ti-ZJ{hY+SbbZgflGOCnBJ0T~nG__2;c}%VnK}ACnZ+g6 cj12A?o|_ezr5G6}OtxleV~6UPe3a!I0D`|8c>n+a delta 93 zcmZ3?y_<W(RwfZ)4WF#UvPAuy#JqHUpZvUZYfaC|XP6WOmCz+77cz_RhoOp24q)_} V?8AJSv3IfpOB)MF|KznS-vFg&AAJA- diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/dao/QualificationLevelDao.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/dao/QualificationLevelDao.class index 2ea3970e656f6e73dd8ac2f98f7da640b427b7aa..6f9f00faaeefea0b361676b0a410334ec66c2c69 100644 GIT binary patch delta 1571 zcmcbjF;k7})W2Q(7#J9A84hjaN@mgLWH4v&=3($*@MUCR35g7FWn>Wa$x19s)Xz!G zOV@YKNh~h5=4Mb}@aJI&U<jPt%@V;K%n-uP5X!?4#t=UF9gCL+7lS!NBo9LrLo_1; zM`m70SYl3TDkB54hNh<w10zE$4}%Fq93z9p<c(}{lVjNVCfl>|i6k&GFeer#b1@__ zB(pQ5@Gzt@q)nd8>Y$Lp$iVCv?99QC#mK<o;u`EM#K6Q5%gqqXkjul6$B;kyGpi~a z2SXtvgTrJO7EK3*a1YlYSA{~ooW!Km90gkiD=>qdp_q|@58)G^{JeAyhEhfbR<Jb4 zymB6f3Wmzb^Vy=*ix{eT7-|@585uZp^7GOWKJemVsAp(kXK3VMXkuud%))NY*2=@s z#?Zdmk==}Wau3%x1>V$(%;J(_r%E4?0~i?ukirjS>*T#WReT${7&b9%W@p#}3XH9j zU3rf%ZlBD>XTrF1vOS+I%Wg&n!^wec%F6yhF0MfePLT?QdW;H;j0`-Ud3mWt;AluK zW@p&P$e=p8kXMzNo#DV_d4AWB4h9AWHU>t9P6h@BP6kE>Rxq!Nfq_Avfsuicfq{Wl zYdZtuMg|53CWdaPVg?35h8_k61|En4hF*p~1_lO324#kR24;q?$vgPf13+r|7?>Ft z7^E1u8KfCR8DzkE#2A<u7#PH$dO)lR3=<g`876_OVq}=Wz`y`=(`2xrnovXa1&|F@ zVBls@WDsRg!eQtXWJ9N57^=rG73|Vw0$M&GmvVt!rN+R`ppImqB;1vf4AYPeoQ7fG zbcPuWEDT+fWdtqiK?Xv+uEW63pv$1dpa(Wo0UR9aa6{D@W-`oTU}R8YU}c!iFp+@? z76>yL7#QY&LwqhcFt`~Q874C@GR%u&n9sn-uz(?&fr$YWN68G#V27)3Vqo9Iz$@go zjX_kX-or<03xl-wCI+va41UZEI~jr)84fU*Z)1p912z=lK4S)M1``H822-&6AW;Kz zA0LAx!$O8d42;;Eu$W;9$h!;-467Mf7+4q>7%U-9(AL>i&k)1Fu#G`fTSq8<8$;qu z1`eV0<qWJsnY$RW85sOPZqpSqfI2OQkzoS^0|PTSZ9xLfia~(Enn9hx2J9#;i0SY^ zvt(Ebb`%4HCBrg?MGQ;~>I_0)hcYuPXJBBEVW?+{XJ<(I&%nsQ%+A2U&XB~;ki@_Q zr9sJzlZjy|0|Ub{cqp&H=Eao^X`m#+z`!sI>P3B_0$-5pb%ct*t`{oN-pNn~58xs{ z-E9n2oKx!OF|e~NXJBClg*L=vVhpSd3=ED8%nVKpLJZCf;tVbf3Jk7D9@K|>P@ka` z>_KJ*9k92U7!(-z85S_CVt{y%mx1XogD%)RMGo+MQwmLy+zgD>h_u4Nuo|A9Rlu2* zfq_SeV;e)=4u+P^4D6t6%?J*9P#M6$Ak4tR0CF`G!&-2VfJE0ZFfgoVU|<koU}o6B ez{s!-Ozr@ayBPK`Ffed2Ff#0A*w1i~K@tEfiwiXX delta 1591 zcmbQKc145h)W2Q(7#J9A84hmbN@kH`XYgiZ;LA!ZOVrOv%uCnz$<IsYW>8@8<zet+ z@Si+~C4xJUA&8wJn1>;RA$0P87B4O#21bT(9tIPJh{<`ZwjxoC49tne$y^LE46*DC zaXbw13<;C>vpOgwF)}ba20L>wq%bnDxVQ#83o$S;gmW_lGNkh`WH4k-mS<Dt6=Gm! z$Oajf!^ogExsX+O@-$XamOMrV%gGJA%G%)`u0gH}g?c%uWvMv|whC4`iAkwB91I1F z46I-#$haaNhGK@2$(z}txXT#I*%>N$7%Ca6CY!LEv(@l0)H2j<E@U@j78cUb^g;Eg z_2i{oKlr)RGV@ZLDt$mEGcu@v9pS?*z-`UQz>-x^n8V1RF!?o`f(#>rm`{FkVoq3M zQD$ONPHISHL23wy!N?Fd`8AvT<bxcxtajFn3`tNU0w*8fF6Udv#ju`X13SY;kS{k) zcI7$3xMeaIuL<L}$@aXqj5{XR@|y5*FzjMv&~{5LNzTa3OP}n=E2dPQS(2du_M?>o zBO?QkXI@@vk#kOBadB!fJHuW^2Gz-hJgUs>4Era)<8=+GXJBApV_;-xU|?Y2WME`q z1@jsi7#Q>!7#SED7#LW!wlgqpWME)mVrYUYW?&FxXl7tw;DIP$Xkln&U|?WmP-bXj zU}k8X+|H*S08-1xz|6qFAjQDVAk84kAOqGT#=ykDz#s<I17fu^bTBY7bb_p6WN2q# zV1T))3v8$+)X;w@hAJ>{Gbl2MGAQ9Nv>VybZVW^97<#}iE#=qp0lAb5>?$<|ZU%KE z10~_Elw{~dHn11Nz&?h41{Q|K$*=h>>OlrVyspE*&!Eeo#GnT@Q~?|u>TpBV8744H zWME`aVqj&M#L&UO1Pg=-3=9mD!67~c92ndTj0{~2j0{tw7^X2WGE8Tf!N9}-ilcZ2 zX0XGRH!-kpV&D~W+r}U&RPW)VwS_@iTgYoCgAX&qPKE$Rh64;<8^8u4+-A(c&0xa7 z$6yL}8zfqkp>E@25M`LjFpGf^oAt99=77A)z`(GYfrWvEfq}siV!gJ`E`~4$hWc#` zqS`t_k=q!eXEJaIB`#-R6-wU4kjlW|2XdFLkO9<PX^adYM=^ub6(qo{7z7xs8PpkU zz>d;_m=1Lm1A`^QT(F}+ta%Kx7?>E;8HB(NWoDSqz`!8Gz!cBUP#^Q3fsuikoq>a$ zA%>kHhJgu6gOV606T@5v28Ma?5MF@IiwhYRLA^MOff?)w1E?2wF=R0?fI>Of7vvco zsAv3ih4Qs`G88g1gzMJtWI*wm7y~N<1A`+2KZ6s45Q8&=JcA2^CW9-I4-McxG+-zI z`%s8Mj$tv_hw==93^N&)FfcPLh2}Vj$T9|ISaK<VduBPvGxZE8>4k$~1w2oyfHN!u z1CJ2LHipt24Aq+%*g;vF5gY}ea)5zBn1O|1B{;3D0+|NZyPAQ4VGTGdvoNe>U}V?~ eCbxpg?O<{z!)^u!1}+9hhCOTy`xp)|NCE(sBM^W9 diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/dao/SectorDao.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/dao/SectorDao.class index e8247d42b70c88de69e88771d4a9efe9868227a7..78bea8e94bce9a36d8c9bc4dbcece568a341866f 100644 GIT binary patch delta 2653 zcmZqG>o?>&^>5cc1_lOOhWi`2+L(m|vl7b^^>Y&Q()B&_N>bBPizd%!-fqanV8`Ii z!{Ec<%gDfynO72)m{Xd{$iS?j>B+(1&&a^+80^f&5Xcb3&JfJQ5W)~TIfunTHk^@x z#l<z)nS&vck%1{QMTmisA)1H5gdv8J!D8}jR=LTsj2x5Svxu?9@i4?QBuwUJRhLd; zWDs#nElJME%u82D%*jzGPE9V!FDm9>NSW-*D$AAzG9Z0&0jnlUCL@F9<N#K&$(vb4 z1+y6$m@`v~xfxs;a(Ni?81g6IWQ}1f1gS2XY|ZA)T`0uB%1{d8mrb6?CdFaLP{G4c z$xt=<6q`C*4G%*tL*3->Y(Z=s3=ND7CX@4+W#bjXJzRra6^iw85|dJM6l@i&zzlYV zCPoH6L`eDM=cRKnv@kNTf~C0_8X4Mn7}^;+7#TQo^7GOWq2<NJ(8bWr&d|fd(96&_ zIgH($Z2}L&M21P5C$on!P5#HJ&S--aFl_v#C7C(;&iOexsmUdo`FWG0xvUxUkW~nv zsj!)B&t_m@XU)hUp`qymb|ab!Yeohy98zG-lU=xl7&Rt`bIWoeSzN&#&X_;>0=E%2 zLJ*?doM)Q?Z)!zmaY?aLr4J~e7#Rd0HiOd<D0C*j;>qJX!NqWr;S@W=X;2)TnViOZ zgz?;DZ9WZ_3ych^;CRgC6J)$Jxt>pv@yg_de4>)q7#V~@3sMqGKxqMz4y+UyIT&tC zKF_Deczg0cJ}s8Jj0|#<8TsWVd-1D;OlDwUU}Iopn8Luoz|X+QzzXI~Wnf^?XJBMt zWME)m)!NR$xRHT@fr()nR51gC5W{o^1_mC80)`n3GZ`2d7#WlqW-%}`Or3m)Up)Y% zmXCp%fq_Aqftx{wL6kujtVfK2iGhJZ45|mjn$0kWfstV@$SOvL*$fN}FgMKu8>$I4 z)KUQ1P(=oA1|<ej24x(E&PO(MK8B%s3=6<6-5{Xl19B-B*j4Hb+zc8>21>$RDak;< zz*>enuz_lV2C)bObs6{>^pFgchZ`u*5XrC*;q6GM3%MB>8RjuCGAxQ>Sj@o4u!O;v zfr$YW8%YezU?-|g-Ylq8zm36b4Ln95p=`{+&0xa7$6$(Nof_0SJ_ZSfr3}j$7{Nh{ zX2)`d6(E;0Ffc4<U}j)pU|_J(ImRHWBjmS@Az&5*zm8BCn87CG=c|2;fn9q!Lp`fd zgrC+H25DWLT?|nS3_=Dw8621yb~3~=GHhUAU|<HP6e$Ka1_lO81~CRJ21y1x1{DSe z1~Udn25SZfup6}?mO<Ufz+l6$66{70YZb#X1||j-1__4M49ggp89=_}WMC0vVs~U` z2vh{87EUIHl?)6Fs~8v<)`A0L9Rnl7a)$LF-%S1}BwU}kjX@OTN}*(Q2SSoEB+~sD zxEcJx4uT|SIjDmmW^TY1WE&YaF|dH6r2!f(TALWyH!<*P?_x-0V1Qa%FO&hbS13z+ zCqoWK0EscMGB7YiFz_=(GRQMTF{m&^GiWkIg53hiCR$LpFfeE_WHW4LU}V?=&vqb@ zt%wwq4G)HGP$$=;B&F?;Y{bC85C(O#6x_`Pa5ro3WRPcKK;(8v+Dc~NXGmd?WJpDF zniSk=QaJL405~m!%7q;eyC+W+5v?yqwo8YJAspSZ90q=dTm~tIJS5Ac;g(6`uncb3 zPKI5e1i-++(9FOLE`QV^{*>OuP{P2#4T>D;9Sr5$7^){5h>F&OGAkq^N*VYW$`~XV z${A!BDj3uls=y9VfS3VyfI34Y!)^vf1_=g!uv3`8X_JG2@ehL<JA(l@WwJ6dz%n$b zY=x9#d*F#?2{fM>Y+_)qM@}_D8NQ(Ossl|ne!8%P6RwMvw;(RAW8i0KU=U(xVvuKO zX3%741-lrMuMFTWHh?A_AqF{yz2JN$&mhRKlwltOGsBj8aA6E8l0nKq=?NmapMe?X z70i@{lCTaighB&+76S`7OX~~OL;cYRE*FHFVaeZxk>LPCqo3|JhE~og^BCA!me(_| zFoQBTq{x7HtCNA5p^HI?p_@URp@%_%p%=+p`fzXQGqiw<3uXo#aN)+ppuoV-u!P|t zw7B49VEW6T%gzwU&d}%puU}fw3fg*bNz1`-2wv~0fGafy1|A`fZ48||82UFeu!GVl zBe+ll#U}%U2m=elVQ`sq1f1DGqDL7R7>+S8FsL!GF&t-LWH<{Z&x6T}VDd7Uyb30- WGu&igVBlilWMsI-aEIX@gCqcS+vb%3 delta 2326 zcmeA-Y}MmB^>5cc1_lOOhKn1y+L-zIvJ%S@^>Y&Q()E4v^U^0TV%{#s!Qjov!0Z_8 z%*Ei#;K$D3&%+SF5IDJu#X&Zhk%7g<HQ1ShA(WAUDKkZgfsrAchrxs)f|0>uaxa(8 z<dv*ET-*$<3{gA`(F`$@HCfde<0c2PN-`!)&SsTmO9ClQp4`W($&$**pgB2@Rc!J_ zR#CxpMh520lwy$1Odf_ThV04TS!1|zL8|i@8C*0bE3g|(?qw6_&J|){WhexR7foKz zCM93OP|D6w#=}s~P{GI`i13YPUP)?tYSHA2Z0c-PJPg$gHIx6b1@Uq*)G^evGc@op zG%_?zzQk_M*22Tk%Fwo%pCgP(SV%+D2Vn##K&&Sxb7?Y~P449~Wbw%?E}49S%bGC* z!L{c$6>@>;DlN&((Fd!t=Qf>uhntU4Y4Su?2~LE#5KlN`=Hx6MBZy%jWw&`YP2Rwp z&9{q-VK>7bc80wmzwMi>$#;bDz~py)8Z3tx8C1dHWWX;Nc!ZHb#x1oZIU_SKU7<KN zxg@`+SfM<#BtyZ|rPxY=k%QqFBZEM2Vi`gnEP8^GK`68!C9woe{M6)0{CbRMCm-b3 zVmv?j4ZmwhI|Bm)8v`Rl2Ll5G9|I!;E11{Gz`&r-z{tSJz`($&wVi=+BLf2i6GInN zF$04DLpK8h0}n(2Lk~kQ0|NsigEB)O12aSC<W>Ro0FYWf24)5Z21y2P1}O$n25GP! zF$N|E1_m*x9uTXaVFCjq!$go(j12t@3=A+gO#&OL2{rT&ilOoh+zbi~q6~^S44sT@ z=wu8-^%$mrU0N)t<pXjl7uZ#*4BQNANCry6T`9>xz`$yT8nA&c1r1^m25K|#Gw2{0 zC=WMKo*|TBD#F{LP#1DDFfvSHU}TsU#W0<LkzoeIOa>+fP;A6AFoT__Jh@CrseS`I zIv_!8$iU5D#K6a3jAWNG)Gj^-QHEIzvl$q{;fiL#9EQ0dXEQJ`EN5V5U}0cju+cfj zAgUwewT;1N76ZSIP!O2GCgkO-eT;!!dpQHEP)NO>))oe7U7cMFVGImH20IxXm>G65 zL^3jf+{Fw|CQ=M+3=9nB3}Orx43Z2s3@QwE3}y`W4Au;GU^i+(EQ7j{fx(7h9@vc_ z)_jK93``6v3=#|r7-lmtGc06aVBln65o2O^WM}YYn8;Aiz`)7GFpq(OVLk%`!y<5C zEM{P2n8OeQ^^Fe$GdOL^ZDL^G#K0>QyNy8<<Yu9Gbmu}+H6;Fh7`PdH!OnxEY&od& zAT}(42Vgx?6fI>~#=ruOq6TObX+iAN-o=o}zyPy11!}KQn)XhH42<9sV_;=qU<hU4 zX9#1EX9#CdVTfSRWC#Ph1(IX5pl)Gc&|;`hXIRd_$gl#Q1+^H`8CD`vQ#w3UR>9qj z<mA<m9L2!E5C(O!6x_)<a3^c;WRPcKKxBMKN=sniXGpAPkYq?ga+?&~ZBm5t$r`xb z;ZVD!;dbXE+pWXI5RPtnCIdf17K0Q+eKwNi(s0YA30c0DVI4GmG&3-R%O-V*<<h$t z3LutC?_en2#!%_2bBuu(lwfprGN?mB0g|r45mnDn#K6x`%pk!~!XU#?%Am$j4t9_N z#7ww@)EPn<)-y0NNHFk&oyG)C${Y-ge;Cx*84O@$9xU^N%3(;Ew*j80mO!(m0mPw@ zL{-0yK~yNk7wkT0y7JS7rL1sWw5$krGD9^3KSM2p5JNqKJVOJ6CPNe0#gJ@h0C%wg z10-n)F~~7&1ZP8e20?~d44W93!O0Dh7VAOEK<N!4xtW0(<`ujtkAq<gymnCmXHNzO z9wClx47EELnm04BgQA-eTzY^)f`LJZfrVi!ICF0UCu5N4b_NE99aans3~CH)3_BSZ l8TNz8gJAM7m^=z5kAumR45t|w7`PZ18O|`AW4OQ|2>_O`i|YUY diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/entity/AppUser.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/entity/AppUser.class index e9fb9bca45ab2cf9833d8ea6508cd46b56c1e4e7..398c5f8475ee1a5bc548a2fc1e74f7d2eeea492f 100644 GIT binary patch literal 4184 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 delta 1449 zcmcbiFj<1@)W2Q(7#J9A8B!*49hTr^;9!v8VUT2yVr1YbElw>esVqokWYC*@n^}`1 zC_g9Fnw>#rvMrPN<jG7w85JfQGK-42=9T8A7A2OXrdTsFu$Cp}l%_H=$bqDUKvE%< z1*z7I3~a$6L7sl@le?IIxrk_J`eY@RCF<uS=B4WgmlS2@rCT#HuotJ6_$FrNFfxd0 zV9INTF*0zcr<MdH78jT27ftS9HD)%@&;+?m9PBbi2JYh2l2Ev#4K+Y=>|i;~u+7I= z8yP1DvQHP_@yyFhEppCDEG|whW@kv4{F1|qC5e&2c=8(#<H=c^BJ~W43>*wh42%rY z46F=H45AET42%r23=9k^49pC23=9nN42%p4sSJt?N({;j%nT~U45AFGQ4DGf>QM|D z3``8%3_MUBQVfg?$_y+ZCEQ3#G(8!#7?>FN;p#-e>cB?v*F%ia^kmRxU}6x0s}TUJ zVFH;Y#GvWPpu@lfiYcf~stimFx(tjAdZ`Th3<fYuB|!?c7z{z~G6E4Ghp{rSF)%V1 zGcYjdGcYnRGB7Z(YHep=+{nPdz{Fs}pvnN2XJAlfFlAr>ThGA2%3#J|&cMLH$iT{A z!N9~|Joy8cs<sIO1A`a?D+2?ADgz6H8UrVTIs+er27@qzCRnc)0}BHKg9u3PWCw1= zdP@cd23}?cZUzPheg*~xA+Ui~4AvOV*}@>a2I?G`YxKY-g4~bn8XE>%kZTwi80;9B z7?>Cs7}&I=w=xLBJj1}i1r{}8U}Z2i1REg+7GMK8iU9=JpfL<`94jM(DA-YU4E7*L zO%~)4QwRAH6k-evW?%!*-Qd9B2y(+@KOXUVkXne1Rt&5R)?oFR7D5sT)Iv~bFfuqn zEJTTvEeyOH&_cluY$Cb~oFOh?V3?f1CyL|-Ck9pqXRr=TH$V~})D586U}SK?>IMOH zH@JaKM0bNL%nkKOQK7w+K>$4}yck#+yun6bItr57p^kzi4>tyPP)wi&j5xZZ{J<uH z9955;K0FvaL2-kUKD4(oh@(3yh=G+M7;FTlqa+zX*%<05Nd^W6W(F^Cw0MI{1%C!s z1|J3n246@~;1|H)U(67|5E#wC=)e%fz{n6B&A?d9z|9Z>DK<hG!WhCC!1-8`AtIF_ ilEHu>ih+qCI+-DcAr@4Y#4&)9Eh9rbLn1>m3nu^+n5GT@ diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/entity/Application.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/entity/Application.class index 1db376ad3094759e8307757ae5055fc14d64284f..129943da2efc4139b062c2506e202a6c51cc7318 100644 GIT binary patch literal 4396 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 M4Al&^4D~FW09BhK5dZ)H literal 4322 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 diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/entity/Candidate.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/entity/Candidate.class index 1e2bc7aa657a3cfe9be477d995cdb16276863040..c84d358a7c81540f2739346a2bd05ea586743aed 100644 GIT binary patch literal 2962 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 delta 658 zcmbOvzLbaS)W2Q(7#J9A8B8~F2{2ApVJhEzi#dUDGAnyD7iV%}UP@+4VoB=cICkH9 zVFo4!CI&tReg;McQ3eJEQ3gf^F$QJ^@nQx(28k#JNd_rT25AN+1|e)pgpic5F)%W) zGJr@K1_lOw21W)(1_lOJt?dkq8yOfFm>6Uk7{JmD3}Os&3=9lB42%r*3=FIc@(c<L z3}D@g3``6l-8Bp>3~USx3?f?EI=b5#*tH~CwlZ*ONwRKb5I`6v#K6M9z#z%M&LG9W z%OK4l%pe0c*bbr>YAyqV2!j&X@gSBm13v>3$Ycf;27U(SdN32@eUMQiU|+H_sDc85 zl|hMtfkBypfkB0Vfq{>Kfk6oDN;L*`21c+e5q{jlz_kI*j|yO?fc%K;MGXc`s2A<P z0q@7az^0|Wm4OT4D=x76R2V?c00om6n9m0H4I4DJK#pW(1Urn8K?~xj$p<*a)DhvV zfz=J#5I0Qz%PF1=QVX$Ahk=zr7pxxBLP*?0Ed+%E6N3)eySm^sBg??dpa+g~CQ!OD QU@$CZFk&!fU<C0@02hcc00000 diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/entity/Company.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/entity/Company.class index a1fd23885096354fe952948017a5a2239e96ceb5..73fbf5835ec6b52d6bce5bc9b046ab2a7a8707f6 100644 GIT binary patch delta 1189 zcmaDL(kaGu>ff$?3=9mm44E6bdKl}u7?c>4*%?%L7*rY57#Re!63Y_xa}x8?^*!@S zQqxn57#Y~h5_3vZ{nHp3#56oLeK1s5gUrz2VbEkyU}WIP%qs~4spn$QX3$}0(B)y! zW6)=0;6rHhO)SY^WZ){v_slEdcdbZFE@5O~(eTprWM?qkJd=@+MNCu!n>CYHvu>+5 zU|?ooVh~~wW?*D6VqjnpWng45W?*J8DP|C2FpXj`V=(t*uwY<f5Q8h>1}k9%DG_5} zVvuBzVqjvhWME)mVz2_6p~}F-V9mh7U{lOs%U~DHz?jNl&)~q|7{%bk;GD|f!r;o_ z#=yY9#lXl=@6N!+z{udiz`&r-z{tSJz`($&wVi=+1K2oE1_rP+1A`)i7Xt$W4@3cj zH-irY0|O%iC)f;h&FmY|G^^v#?90H!#NaVGkzKVOWRnmB3j+g#76UtjHUlq%4udd* zE?A=-0~-Sag9y}C5X%o779f^CgD?XVST=w`n1LD00tF_>BoT0!u`&dLf|ZrQkAZ=~ zpMilPfPsNQh=GAY4D5v<um|f={ji0B4?O@3!A?OBfMA9Y1}1Rm*)cGI9m1xiy_JCv z5dvIbQBww11~ae;pcn%MDkKO&ERb{S8Nse(Wdys7ks%c9Ds)GQpgYPEY$Up)!mv9^ z1l>`#46F=xU=zf&!LDS32QeEs=-`fm1XVag1jw_K^*F`A@dZja3=EE76Hwg%NsN&U zj0{l>(G1Ms017}3AaRgem>EE^4hbMv237_)ut}H!1WMqH3^DNZ$HpL8&tSy>cby~y z12p-^GB7g4F~ma?bT|VOIK3)qZD9~U#vlfwb}~paGVEYb+{U2Zx}1T9a|eUg4hBU~ zxI$b5@slUmB@jO;K?5ZLlr<Qbq#Rfnm>9GY!AU#`oZVy@m>H587#LC*m_T{bmLav6 MA&nuO0h~iK0Il$V#Q*>R delta 981 zcmeAadmzGf>ff$?3=9mm3{e}odKj768I(4wF!8alFl%UfP4-~hR<Fpw%)rDT#30PT z$e_%?z#z)N$e_Z&%%ED#AjF^+#h}ih;mM%Mz{DU1SHcZe!U$3##=yiN$sonR#Gu8% zz`(?y4K_oSfr&wffrUZ0m_d(0KbnCtmBE0)kijU5!I;4$mBEz3jKQ3Nfq{#Gk-^1+ zfsKKY!IFW2L7#z<fsuiMfmLfe1LFp;aaIfrU}**hMFwjI1_mC80tOofTLuOOMg~r> z8J3d|vWuxB)T-l9YsbLEU^)38yJ|g1tq=nX0|SE=13QB@122OPgD`_GSf3pO8v_G_ z2-FS`%O31=5X*r<n1Kl_>&PI?zzk-Af(~So2sngT8Js|Y$jV^Pz`)?Zz`)?hz`!8H zz`!5|cCa(pJ9env-NL|!?p;H$Q_#KZ!r%(_!UGO*uot+%jxl9mWiSJ)2gL>`z#zc` zVu2jT2zC-HBiI>?3~pd2pu0c>-369l6VYAZ4sihkLp=j1G(lm(rlq}=L1ZI5z95dW zWng8n0~>+qC`d{Ig%c|yG+aCwJV8D~Nd;RN#L*q)2sRPqC}{=;R%jGKQl1wBBZD`C z4+ArU<zxXa<nVE2U}bOvYs7R4DD5&b_`;JG8-pZ+HUm7wB*7^Jl(7967#aK-0-#CN zn}G?O62-K(Fo+*x5CBm-8KfB*T9-4haIR%wU<hYs03|X|+Gb$z1ltaBHYgoHVkZ!k u(ixaU99S5b7=pl|7YxokvJA`&Aq)%*p$tr*9Hz$*R?HC25WxV>Z;=4->r3qb diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/entity/JobOffer.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/entity/JobOffer.class index 1821fd1a0edd009d51a7d0cd1e66947164735014..da220df5c537325548cf56d56761ddbf1b61d035 100644 GIT binary patch literal 6045 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 literal 5941 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 diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/entity/Role.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/entity/Role.class deleted file mode 100644 index 9bf8dd8acd032b883c07ca8bb9c1d4057510b7d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1215 zcmX^0Z`VEs1_oOOQ+5U>Mh2a<BK^dYoW#76%)-)C{mk4F{mi^HBSS;|to)?hyz~<N z)Vz|+l1lxc{G3!y21W)J9tKthHbw?^XMf)SN54o$20b5A%&-O-!okD92{MG!+27B_ z)5S5w6(q{d!@$G9%gDg466WX=>Ke?*pdU?|*<1|#3<B&7ygUqo3_^?ytjRg~d8v#H zA{v@LS&3zd`Z<Yt>H7XjS*gh-)?5rC45I7|Vmu7u3=)hCd<X@ud8N6G4D4lzIi;!o zX^ae}8c4dGa}tY-t&s$RONuh{(yftg02u%_Uy6r8nn8w<fz2i}FSEpskwFaED5y?P z%`grIRxSoc1}P2(P7tlg!=S{V3<?Oa8;Thj3^g=K3<^dDHkf7>=lqmZMh1SL%)C^; z(%hufqL9R-oK!{zCoHbfBrU`l8H9ZDlM{2o5{oiHhC)qd$xF;l1<4^o73y0?1_9sH zl8pS6fW)H2+|-iPqGCn{mXeIjVnzlomDIe_Tos63UI-_@C?zv5F^7?XGdMFnFR`Sw zD3y_cSwl07k%7Y|IR_f94oDFK3Vj>Wd|_wJ$iNkxUs{x$>Xr!#8BS1g*8@4;!H|KG zL4<*Uf!RS4l=2xE93(-Bmw`b*3Y1(K7#LU?I2lwJlo=QqR2di;lo%KpK!9r#1J^bN zz5@*W8yFZE7#Y+U7#P^Va?A{z4B`yx3`}74CJanqQ7$1ytsM-)2N)PZ>X}f~YcMb{ z@IX{CXfkLqFfeF?>}6!oY+ztu0;MqqX0Ty~49pD73=9nHTG}$(86-C{fTWRaMwrCT zpu@lhHHnjnL05o*K~I2zK_9HeK!6LB1fgEiWMBdLh(VBbF9TyF^Ckx7;7FED4D7)m zm!kTNgTau&0IJOqs!ee>17oC+Ec12-xy=mBV2(Tsn8Ut<L18lk7s#0qWB9<XXJO!F UU}cbEU}j)~y2psY80;<+0IUBAO8@`> diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/entity/RoleType.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/entity/RoleType.class new file mode 100644 index 0000000000000000000000000000000000000000..7ca0e068bca927361b5db70ea72b3344c1fb3d5e GIT binary patch literal 1243 zcmX^0Z`VEs1_oOOQ+5U>Mh1hlBK^dYoW#76%)-)C{mk4F{mi^HBSS;|to)?hyz~<N z)Vz|+l1lxc{G8N~%7Ro*21W)J9tKthHbw?^=ltA)#JoyI1|uI*&9Md<#KFVB2{MS& zIWaFKGbOPEY&<s)0}lf)BLllin4?dqYcL~&aWv`Xb20EU2(UBo@-PT82r)9SCg<em zr7|*zXlVLmC6*=X=OpH(>-#5Vr6!kHb1{f8h_W+?@i2%pNH8++Ar!dgmF6-su$Lv~ zl&1QpF*2BHAnA6_Nh~h5MiK}vDay=Cw??)BWB}NFDINxC1{p>MHk-`6%o00B1~Fu# zpgKJ@!#EgNxfmE3q&OHjL9`+dgA#)>C@{coC<Zw}lZ@bCWMG3Cz~Y>rlFG=y?~|FA z>Q|bZlv)&$n3R*s$l!*>MVh3CJR^gUPkwS@PFP}5Cdgo@=`4APxv3yIMCd|&&B!3& zn_7~QpAwK*l$e`Zl3G;E$iPyPky*^hz@?I!SDLE=(aQ_r<QJu6<|XDZGH?cGrspM= zloq8jGB9gshA}d5*d*sb!`B5VVnDHAL%L7wtQi@&g7Zs@l2hF>K_SEo%LaNNPdFGd zFfxcRFfcGXNP=<%1A~JkC_ytY2uOjFFarYvD+4Ek3WG8OBZDdf1A`I+BLfI<ZDQcs z#=v)gfqw%70|O(28Uq6Z8(5B+fs;X;L7jmKtlory2`tJb#Hh7{LHGazBS<|Hih2zO z1_mC8Dh5pkEd~Y#ZIHc;44MrL3{0Rj$G{9W%#eYZfti7Ufn7^mW;=uAMh1{HvdsvS z*co&f*q|nHGBM~1Ffix|FfizYwHOF+fs!QDOPUNUARjRZvhHPIjAY)#z#JUOvWbB` z801n^pK&l4G8jO$IYPB5?q*<&6q04$&LFp$ff>w^X908AcQ7byX5a!j6JiV>*!3(7 WybP=iQVh%tOi=e2F&Km0WdZ<am==cs literal 0 HcmV?d00001 diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/service/AppUserService.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/service/AppUserService.class index 9d72472a36a6ab69616234ea7df7a8b0f74d3aa5..c8be8b3025cebc0a2a2dc1d824bfd6407c080de5 100644 GIT binary patch delta 17 YcmdnNx{`H+A0rF1hNkD{M8<GN04;U|c>n+a delta 32 ncmZ3<x`TCtAEU64hNe$eVp*boPGVlVzE6H$y7lHD#&AXerKt({ diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/service/AppUserServiceImpl.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/service/AppUserServiceImpl.class index 3228527ea8ab8d6aa2fbdc25684e9fe6230ed4e0..295eaebbc0b1d2449215d5e0a4aa3e42c0407908 100644 GIT binary patch delta 446 zcmZ3_H;a$!)W2Q(7#J9A85}oqU1t=QVPs%U&M(a?;bIVCP~~AzV^E*W$rQ<=$;e<n z`6G+sWItxH&2yO^GbLy+FfuSPa5L~QFfwQ{Ffgz&FfwQ}Ffgz(Ffy=$c{&UX4EhX= z42&RAt?dkq8yOfFm>6`SiWwMq8T1$!zy^S{=`$EGFfcGO7%~_!Ffr&%e!wcLk5DU& zq?U&PqShE}o(TgZgDHa<SdR{yY&}AcI9QJu0~6R5h#ruA<_s1Lj0~0_XE8FEgDtgU zkYQkC0J+!*>S7M9Ee!127!=olEkPJ64K@_yYX$}ms6!Y*;l#iU)}L<6zy!9D6J&t) uHU^aqXeP*mO@KIy6KVnj11EzugAKx2)(i{`5dVS9vjZDp4-aSu1_l7}SthXn delta 439 zcmbQmx1Nvd)W2Q(7#J9A8Jsq9U1!u#VPs%U&M(a?VPp`}(DcblEKAhSNz6;v_sP#o zx8`CHVo>8@P-oDX{Dm=+QERgwlPt5cCIce_69YE`4+A5EHUk3#8v`SQ4g&)ND+40~ zE10J{xsO#$T@Nb9z`)C(&%gjy4^m~oV93D0z{p_4V9db8pgZ|AtE@gktuT^W9tMb7 z69xtbCI(XmMg}tmbFdz9HraZF9&xZ9F$N~EEf75*`z#nN85kL?KyG7XumD?X&7i`- z$e_!>z+l9{$iT?Jz`&ukg@Jt=gW?9TB?v>M!G?l7$-uw?bqFIUEEt%<`qOP0n7}r2 zf(+2!#-NIBf;`v+h_g7MCNMB?GT1QKBAjKzz`y|UpB)1egFV<04)AbwWMBXQbZRMC diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/service/CandidateService.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/service/CandidateService.class index 968568c5ee8c9d52857e324fe49acf398ffbd298..fda6c0c60ab7fb4da13c008d3695083cbb03be77 100644 GIT binary patch delta 160 zcmZqXSj{PL>ff$?3=9mm3`*<_OpFYclQ%LdZZ!PD<Sdz<TH;txkdv95Sdy8a=TzyO zn3s~7l30?;$RMoYsp*rISeB??T9TQg?~_?vV$I0FoeWdqnZn56rQtbox)nE)5t9{| ujTt#7hcGWyWnpBHVUT5DV&G<AU|?lrkYiwAU}s=pU}BJGU}R7LvlIa$%O|`5 delta 50 zcmZ3@*~}qu>ff$?3=9mm407xYOpFYclQ%LdZZ!PDG<g%V9wW!(hs;a47#JC37#J9s G7-Ru*!3<3R diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/service/CandidateServiceImpl.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/service/CandidateServiceImpl.class index fe49a05345bec1cfe13f4d8ba89a2155edcc41cb..8b62aae27c2c7120da87901f1a90f4051ca15421 100644 GIT binary patch literal 4206 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 RkaU4FcQ*qpb@wnZ000GhK0E*b delta 1423 zcmaE-a9l$C)W2Q(7#J9A8B(|ym>8JZ8CZB2SQ*$RbFnE2a58W(aI!OS@i1^R@Gvsi zPEKG{oM@fQ$H&0W&LF_UAjlxZ$Y3~m0hi&#yNVoq3?e)Xq6}h_*%;kK`4}X47$g~_ z7#Y|LQj3Z+i%S?8%qGV%nhJ(-F~~5;vNOo>Fvv3~fb=IY8ct?sQs&@eP~u@wW>A@I z#AL;-#-Ps5puxkS$)Gj4oJm~=q)Uf~L6<>~k%2WmwZt=pk%3u5(-S0az{6n3V8qD4 zUYuIuo0yq1c|Eg)AQuB4g9#6VDT5g!18Z(-QTpU4#yn|01`8erOOU(Ric)j)%Tl=* z)EKOJ7;G49C$q9zFtan*Pu6F%o4l7p#?rzkzbIY5xS%LAFFma&F*mh5zbIQjDK#;# zSU)W>xg@`+Qa>>-FTW(QBr`ux-?6kLzdW-jHN|>z9b2WKKzeG4V@{59VqQvSN@7WB z@#e2=Cm1*1<nU#j?8zNk&%waNz{p_7z{tP^3SI_A1_uTP1~vvp21f=422KV>P&6<w zGB`0XFz7QdGB7eQFtBQEXJFjOz`(%7;0#sFz`)Dk!oa}515v=>%HYPpz`)4h&LG6V z1lDW>)y$!_g@Jt=1Mdc~{(6J~B1i^sKn?H!EAnJuWbk4TgBoPZz{J1=avR7X>1_<c z5R(uFNrDXmDF=A~;t~+co56>Hk--<_0Y(OIu=D)t8Kj`bIwBjZy^TQ};XzQ6WMGhE z;9!u)VYEL(0K#a0u+f1GK@7}bpZ9`NGy?;JJjlV?+Zd#`F(~@#Y-3QJ$-r9Ayap;N z&a#Gqfq?}aqGAlJ3=9k^3_J{K48jcR45AEL46+Q`V9OO4m_cSi1Bih^o&jPx4}&m6 zFhd9f6GJF89v~uN3``8+3=9k$42*vmMA;eCU?Bi<rb9h6{vyC`R%TFvx)~*qwYM=S zq6e}8*fE%a9LW%c2#H7r1_nqJMl&!n#K6P44jLj#TJ>8P*tNGYXm4W>_SHGYAfl}U z4j+AJpcsdPVu2YcP)r$k7|a+17%Uhh7%UkS8LYwXfCP#XJW!P2fg;8b%n*Pa1d8ko zGKe6k2PM*2hB#;<$b&je3}injZGjxc4dUo*V=za!MTmigfq}t}fs?_WftSI7L6pG> z$sJ;FcZe}S5*;T4FW5z>Q6a<N3krW$SZWkyh=&G0$i<*&<^x#`amNk@D?}VXQlu*b oCxaW3y?k(c`QY}lLhVIL_K+A!U|?cMWDo>p;v{&UPi9~M0LPlHQUCw| diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/service/CompanyService.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/service/CompanyService.class index e2861966110f6a9d7e4017cb32fa8e8136582dd6..f2c0169789e097e04bce6f7d79188352b10e54c9 100644 GIT binary patch delta 71 zcmcb`(ZI36osn5w!*g;ZqYSf`hUeyXMs`MK77fqI3z+1<^aUmjNns67O`oj9vPAvT XlFS@^pUmPCYeoil4bRQ`%-W0qj3N>_ delta 190 zcmZqRxW%!-osnNn!zU}TEKxrvF)v-;Cx7xqMkx`d%oIk3NDK+_$+e81orF=v^U|#~ z!x$OFlk-dSO1$!u{L|7>i;A5po%3@G67wn<88FpPp2DPIu7qg}#37|6nK}ACnZ+g6 Rj0|BIiY9)un#|Aq5dbZbKbHUi diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/service/CompanyServiceImpl.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/service/CompanyServiceImpl.class index 8ad1a9e13a4bda5e25887a5aab25d99113405907..9dec0beec750150ab9b42b2b561765e0d2f5f8a5 100644 GIT binary patch literal 3159 zcmX^0Z`VEs1_oP(Oil(S24;2!79Ivx1~x_p>$D>M#FCuEypqhq(p3G-+!FoFyfh<2 zL;bA$q};sp68+-TqO#27RDI|C+=9fs%3uiBGq)gzk%222BIuHs&&XitL(r_0#C(`J zE{XZpTnroxoa_u-JPh0nJd6zbB<g2mU@1;4OJ!t8(I9MfYF<fZNhQp7YfX}MfE>%m z!@$oVz{tR!mYJ90n3KcEAf%z`la*MOs9#!=nWOKMSzH44l@JeuFoOsq0|!W<Q>AAL zBZIhxC$duif|AVqyu=)^axoqTaRv!S1}=zl-^9!uMg|QHh{-vLdFlGWB}JKe>DD++ zkm6yGW{_cIU@J<^%?E`Ei-xCW7(0U;BZE5FePB~U^NLFg3i69eQd9g3Qi~EnPH?SA zP6fM#gF%6jK_xUVGq)foH8(Y{BsE1LH?<@qKSe>kv>+w1Bo&$j)VUaB8I*V!lo?bQ z8Q5$x^D;~97#T#dIX4WH+|+m&)EP7w8Mw3Zll;@tQj5UJ#fWeWfRc+BLXR~!gBF7} zJA)1ngD!&}BZC1^=>^FEMh1c8{L;J<r%I?#K=C35j~8$ddFGX*rl%HJb1@h&7_u`M z@h})Om@qO3A_5Vrf{}qEGp{5pF{d<@k%3u5(-Y)6Gad$W1`9?8en><jStAU$205iz zF<7%R*zhpeGT1RP2!ZVYOT$tJBLiE0k!wzIDkFm$a`^fuWu+#Uz>)*l;ZPxV1_xqt z5^8qfVsK(`W@m5##i=VJg8<keNTz~47{<=v&d6X+Fwn6V7>o?upv(#l5k>|RGV*K~ zBLj<beo87M1HVsZUaDVdZc=JdNMce>DkFoCPkwS@PFP}5CWsFeWGTtWEM{b|C7jf- zI~r8VSu--QLyI}0$~e@JwPs`x@C9e0fW)H2+|-iPqGCn{{`Axm$DABUU}UBiGcs@n zXQt;RmXsEyGBUVglqoiZJ%DDdoi!r^Q)UVy1EVJ+gBNPrgC#EVOl8Rhmrg>Mg%cw~ zG;$inQ)H26GjB?2PAa?<Vr1Y&DU}!*#KD;xmfwrvxtx&!R2X_PGH^oU5tQvk(^E^Z zDRoB;D^Q6}ylY_wT7!b0FFmyc62p~{8o&f(G{NkH7Lkk$X+-1{P)P~33skWZPA(t~ zFxP@~XofK|a0Ta=7A2>;WrFgPIMg_#`d$xIEHE-i29@TOWag%ZWfo^9<)k|1<>i-v zYm#C{1`VJ5qICV@0#Id@21@Vc`9<0K#U-glsre<91*!T_<E$AJ8JHNH7#JDc7?>CY zKy48Q1_l98J;A`hz{SAG;KAU@z{KFiz`)?dz{J1=64lzmz^J{AfjxX90|NsSgEvSF zEF;Ch$so<(!@$5G2Ig}y_%ir0fB+YRKSKZmBSRnq0|P4~gFgcU10zEag8<ZMBL+qW zMg|534v^8?7<f0p4Q2z2%YzN&0kb)vh6aPxgfK9IYIp`_uoG>ePUHlcBfX755ZxdZ zut5;laY7AZVBlm3We7vKE|h_R0b+VM10$$GfX#I6Z49F5rfcFbJ%S+;VR{7I^e6^K zhG+&EXc#!6xm^-HboCfG81!-2fZ={dh8TFn*)cFfqGLCMY^0F<b_T`$;IIQl93*^A zkc=e|X|W7(PzS@(1E1Cw236^u44O;~+ZgoMpc!q6WHcW<#Q7NFkwZKloE{PwETAE6 z4-E@$sL`fO4CrBPhh#1{++1!<b0IOA$iT>u#E=Yix(n2N0a!X=+{R#efWaQ!5+@`} z1mKnkU?vwv24#j6sNue#mOTRl1HaaG21hOJtqhD>I$If35zz?=a{;i~3JlC3rEs(P o!C4TL7x=-MkO`b+IGGq&!MPHYVN$^%oW_vOz`y`9DuaOm07TOS{{R30 literal 3284 zcmX^0Z`VEs1_oP(WG)6K24;2!79Ivx1~x_pfvm)`ME#t^ymWp4q^#8B5=I6#o6Nk- z5<5l)W)00SP6iGJPId+^9tLg(9!3W1v?Be)lAOf6lFY)=RQ=4{68+4)G$TVp{jB_? z+`RM>{o>T3vdrXEedqk#g2cSaU<lVUw;+d+fh!pz=#rSv$YAJ0(5#fie3&^diTTzb zxA5^W@G}T7GH_?*C;6wPr51tBG9u0_FN7XzE(Re6VRi-)9tKecF-8V`5*@?Hz*3x8 zmdePGqCwd1)Vz|+l1i9YtTjp20dlMa4}&Cw6e9zBT4r8~V@?hugOG-%4><ZtOEPox zeKLznz`l~<VUT5zV`Sg}DRipzOkreD(|{-h$Fom<Ub;21PXB_E%>2B>9I$Q$9tK4Q zB}N7=h;HA+%p67r4P^bnB}JL|Oi<xrP-ReKWMC^w&CLe|kub7ui0xtQ3>u6K>R{J{ z9Tl2aTv||&UsRHs;$M(jln8Q>YejM@*l8RLT8s=Tp?R6P1v#m?sd*)-DGIr%B^miC z3hJc=DTyVi(A1{R#h}ih14`Puj0~d4*1{Ye#?7G5V8G5`$ira7V9dy1KvcXVB^*Wu zf#m$syb`BMsGXqj#o|LxkVU3E3}y`Gj12scz(mragzPv-m_VWkIW8?3tk@Z>c^GUM zY#AAZz<~vphD8`716zKPYff=0S`0&SGAxe4;R_XFXRs$Gt)nJbb_PdA26KV|g+1pp zGNciaL(p<M;S2)OfD|j%nqiC#EYA5Usf-N#KACx`ex<odsYM})Nja&E3_?En$%#2( ziA9+pK2(sUBqOt!k-?U*UD!PaD$=YO83cU62{9nCC^0v+B(<oRkwG%3G_NExH#ID? zI5R0H)iEzGzXY71iy0X#eDaIZ^@|HYStt#pr98hVTR$l^F|SxZEit(yzo=3_5w1bs zv9u(=JhLb@#hQ_U8&u#xlMo|=2@#=+8javEVuu#NM3uj2`WYGc(^E?vb8;Y#$V@F} zWZ(?WOwUU!DJ@E6WN^hOk8B7BE}9*7){G2HnJJ760+8$gPQ2j!!N?GaH_yN_1$lO| z<bn%2AtYO&MFt~7G_t)=QEcTWc{cN=q~@f;OE5+TUX&7zkwF|>+`!U$F}&PiWZ;Cx zC8z`vO;0VsrX~#8?ci`kb~&idAl}t5^Q}RV#Fw600!aszkUGX0i%kT}47Av1WZ(+U zFD*(=b;|_hTXCobNR0?RP$|gBpn)|T6_=zIrRJAZ7NqJ!wOBJKGH@_3F)%VXFfcJN zff_Lk3=Be`HUt9$0~Z4$11p0Q12cm&0|P@OsAa^!z`(Dyoq<tHdn*IGmd;iN-i-_l z3``6zU~Mc63=ConEDYidoD32Sd<>Edt_%ze3JlC3Z47P<?hGKn54MI8EXL0uz`(@d z!N9=439_AmfuDha!IOc3fsKKY!Ha=`fsw(R!H0nf>;fmS8yOh5w6-v?Yj0x^3`bZF zYPT^k$TDy;$bl^tV_*XN0A?u{gD-<010&cfRz?P2u+jbuQc$Cf7#JBqF5&<gy^TS9 z1Kd|2hcGZGfeqz>sDb(<0IVjEff3Z6fCiE+)QOxRbF{ZHNTVC14mJqlI!>rT3=EtM zK@7nN*99>!FhEQXVPFKcH*lCPk8ZjS4%0&!!Vsp1!c7lnU}T73P=khnBbwWl(L>je zfrG&ahYc9+XJm+kN1Po4GbB28GpI)jX>MoG-VY8tP^y81uQ`&jT<~DQ8fj4s#!v^_ zLmkYmwS|FKdnbb)6T=!bb8V2!<%XNf%@B<o+|l5~5W`>&4Q6z6O_&(a1KN>Tb0N_f z%fQGG#}E&7w+qzW0<c8FxQ)T$0D~R6C9X)82*4dLfSFd97!tt#PK0NjBnAcmTt_h! diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/service/JobOfferService.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/service/JobOfferService.class index 26391a03bc3ab02727f3298cfefbca83ab08cd5f..6f3c8fcd9716487419fb0ead7d4b7323b6d037ae 100644 GIT binary patch delta 51 zcmdnZ@qvB9H)dfC&&eE&M$GOSo|AnTgP6rNJST5plwtM;(;|~UG0tLU(eT_nk7+I= E0E(&)4gdfE delta 144 zcmeyszMEshHz6erpRB~PME#t^ymWn^{JeDQi9d~m!Z1W9>oEp7s9^|eLUfmwWajAm k7nEe?=OyM?Gcqt`rZ6%@VW^(Gh;f~eFoy8vV5Yf@06wEJ1^@s6 diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/service/JobOfferServiceImpl.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/service/JobOfferServiceImpl.class index f6bb8ccbf30bcc046b83cc026ed53ce875ef1e96..d5a5343207922c205e653ed28e7dd87cee2c2beb 100644 GIT binary patch delta 1398 zcmdld_ehrO)W2Q(7#J9A8TvMI$ujzJF^DjTvNMSBFo-iqFfs^cC6*=X=OpH(>wD&v zq^749F*2~1CFYc-`lm56h-r9g`e3NA=4Rkykm6yGW{{a|$Y{YW#~{znpuoeR$e=X2 zgwdE=g+Y~_L5+t&ok3&rCPqzREk*{OkfQw3vcoG=6&}rL<zUd6{EktaO^=5`pTS@< zH<Jvrx`yXuOD1(bBL-u31`{3zQwB3e2I0vQ8HFb|GO2J|Fj%rPSn)7eGuTYtz~rSQ zt^svcNoH=UzE6H~Vvb8<Nvbs$g9U>f4}(2}!(;<yab*b&h~v>zX@-G3;Kak=%-}LP zmDz~VZSo9e1vU>J22TdB$@`c!4SjeRd>Q;08TivO^HQ8Do%3@G67woOQy3Y9;UQ95 zl9{9LlUZD1&CU=oS)E0PF=%oKizc5xLkJH8D}xz3Ll`52#pD8Z#m#G2W-uy>qw4f8 zD9OyvOU$umWMIlnVPs(RoZQSdTQY}{K>!ip!6ikRdFhM{-Wr~hqu3uavuJou_UF)I zcGvKn{Fl{z^CgZ_M!rTah9-t)c7_&EaJEhk<vPOHK3R&}iKUB?L3MH?mns`OLk}Z^ z+~h_sxydWJh3Z2Y7#V~ZxEXjD7#P?Y7#Ua@!WozuA{ZDL+!&Y`m>3usxU{x2FluRU zWnkaPz`(%75Xr#6z{kMMz`!8Fz``KPz{w!S5XHd2AO=y*5X}(700LZKQyCdz85kH? z85y`47#QLh7#P?X7#ZRj7#J8C5*UOSn3)(NCfo8z*Mm$2Nii@;flcFKU<BC>H7yZr za}on1gET`j)O{Nnn8B{l(%QnnF1>?6cqfA-6NB$223cvHZ4AmY890PAmou;mY42jt zWnkFGV5qZ!fq{Vq$^CK+d<^mo8Vm{yQS}TA3<?nApjI+4XfdQPq%trv@G;0Rq%ov3 zFfn8>Fff1|1`)|*U}DH(U|`T>VEWG>$H2_az`@AQpu)h!&S1p&pMen}!VD5&XUK+y zMG6B01H?<Y42%qU4EYT8OyDptfQGrU_BICd;|$!|AbJ~vEy#=7I~g3A7{WI($O^e` zV=x3o1~b?*VhpSd3=C=v><sD*{0y25QVd!QN(|cIV1z`9GTcwf3<V5@42)oRGcz#q z!(3Ef0FII(hGGVYOJK3j2eKLxy6#L2AWI=BfD0Vk<_x?H7DyKH!7bv01|Br_K|W?= zC}Hq}MxH&?qugNQcQVv_Gckao2VyuRA=n@p%ndh~o1qjOD^P<=;Xzr(z{pU}Pyw~T zj)58MLayBm{*gj~+ZlrQgVPWqojD;H&lL_gpNpZAp^5>XY%AgBS2M6M)PT*Xg(u`X g1_lOD(&1oWWN2VuWN2gP0H<0;hE9fVhF%6q0RCLs4*&oF delta 1478 zcmaDPyHAem)W2Q(7#J9A8G1Kz$ue?rGw?Bp@Gyunh)phKwBVLtkYs0&;$e_xkeR%L z(U@C~L7tsKfrmknL22?gMoj}1Mh2dcqWsdb!z)u29?faxU{GUZ5b(>-Qz(Ip&g~Im zU|`VTVbEmIn(WUcBV?xGla*MOsGpOVm#*)VpO<buIgd%5TZciHok5R>L7%~3@&YC+ zJ|hNWb_Nq322%zzMh1h)ikupg1(@Z{gfuiE+Dl6^bM##jOH!@b87vqX_`qhu1-Tf^ z8JM{kj2NtW7;G49CkHZ%%Lzj*hAXnx3<G)Co`=DK!Etgovk{~7<bBKvY_2>EZVc{| zpD=4Wc=9lKF?cgF@TX<wr8reO=jRqA=2d#8Ffu4XO+$DC*<n7J#U<A448D`?SacZu zCug&0a{Dj@@-VP67_c)0Z=TPxhfzok!-UBO>>`rZj0{YfDU1vP5Pu@fpIpN>+a?p{ zRk%1iLpCFW0NDFrvEY)T%)E3)hA3>tKNf<80$431_{BK11jEo(PY&Sp*xbcY%E;Hi z#n8ym#LmzRio=%4yj({Z+a_P)a$@OZWKf-K$)(E7&d@#Cf!kO-h=Gwoh=H4dhk=2C zoq>^ol_7+InIUv?7q@hM7y|<X9|JQ31A_<y3xg;FCxaM6I0FNN7y}an0|OUB1VbbP z2ylV*GcrUmFfgz(GH^36FhnyjFt9N&GQ==2FfcO2G6*p+F@!QOFqkkfGBAQPYi(g* z-^sws$gqKdfq{tuWEDs~1A`RU3LXYVhI)h@abQL942%q-3<)6jF)%P}V_*imO&w&E z_D%+2CI;V44C2~4+ZbeLGH?hfFK1vCQr*R%4pOSE1G0w&Y!Adsatz!I@(ijB3Sj#b zAV$Hxq|T7YP@lvA@{9~aGD8Xj6GJKk0|Ure+zcR*GzKPybOr_nLk6b*4AKnD><k?2 z3>oYUatuuD3_9!#8UGm=kwuvyqU;P=uwY7LU|@jwD~ExRA(tT!8bnPD%;4}ftk>Sg zV0fHCQhPUpdE|Bm%WVu+NKB}&?U)$CH!+9{Ic;Om-pIhfzzp`H6ayOr1A`g^JA*od z5Q7GTEQ1z<27@+(0fP?MyO0<(gnQSJA)lduff4K@W(G!Cup8@H85#1y@mI)D1a+J< z0~6SB+)(RXm>58|LL!h0EM>vK#bAkK3pd;rZiYk#P?$o~2`B&<8HyRap%H42W_G;? z6GrN=#bIy>vcV<rkS%3kWGG`Ohgx6<4Oy<;3_g)Ue%l!W_Jh+EBJnvR8P63CcQ_YA z1w$nRJb_oh&97o$VW<Y1Q3Fr%wG0dlbqov)9N;9($k58r4odJ0j0_zNT?{=8k^o8^ B_J#le diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/service/MatchingIndexService.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/service/MatchingIndexService.class new file mode 100644 index 0000000000000000000000000000000000000000..93d201cfce40c6c46b25893c228e9250cb46f360 GIT binary patch literal 10294 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 L5n~Bs8G|GMx>guL literal 0 HcmV?d00001 diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/service/SectorService.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/service/SectorService.class index 16a09ecf2262483ee0bebe3ff3837ef520291c49..676704a05351c416b8605f7221479f20fceead8e 100644 GIT binary patch delta 214 zcmeywKAWBE)W2Q(7#J9A8DuANrPxboX!>L&mL=+!mSpDWJLl)*q$ZbS=I2>6GH?cG zrspM=loq8jGI-%owsBz~qlJbBk`X?c#U<9b%`8q$F3B(QOetn$$e*mjWW<HAa^gbM z$zPfH#YACt<s{~%>wD&vq^749Sx@F*)@9_FY{oo^Uy?zJfq{XML7IVqfr&u|03|&_ Ac>n+a delta 188 zcmbQu{)wII)W2Q(7#J9A8RRE&rI-q7X!>L&mL=+!mSpDW`(zfESTizk24|+{C6<&H zr7|+OU?{NhnJA#R@q!*BqtfL6jItsyO~t9nCHY03DaDKonUl?!jMzXDV8s(J2r-Lm zcuo8u&g==IL?)XtnoeHM#LvR4q3JdG43jP+=j2~ZlbAUfBqs|niwH}xFfvFp$S^Q4 OFfzz8FfcGN$N>P7`!kFH diff --git a/target/classes/fr/atlantique/imt/inf211/jobmngt/service/SectorServiceImpl.class b/target/classes/fr/atlantique/imt/inf211/jobmngt/service/SectorServiceImpl.class index b57324062d1f8c4154ef0ea63660752754e0d559..fa1818274996582b09f5a97b95b8ae84385f01a9 100644 GIT binary patch delta 1131 zcmdlXx=ont)W2Q(7#J9A86q}vF)>O=XlVLmC6*=XmzHGa=sV}<<fJB-Waj5hmSYTH zw3(dGXsC`5wB};qW02)xkYkW%WZ+55%u8|1$#JUmOetn$(3pIXQMMjUts)PD5`!`$ z18Z`AX<i8<1G9#vCl`Y%gBm-7IuC;egC-+`AlUYt#JqHU&%Bb<^wc6o2KKVVoYGYP zG)4w74Ns{1kX3-)q0Pgf!=TH^z*3x8mdeOrtl^VZH2D~lvA#YJg8@i_t+XH|u_P7b zW+NU3V+IpO2DX&coYaz3Mg|rQPt7oP1~Wzmv&sH!ij(<SL?_2Fzm)L86&${7rknd% z_!$}VC(mOw;zIayvM-zIWKK4IQBjyyWS35^XFHT&%D~9L#K6tK!@$U3&cMLH#=yv6 z!N9=4$-v0K3g%fdFfiydFfuSQFfg!cZD(NI$iTqB#9#$g%)r3UV9me)Hh_VFmBEI= zmVtqRk-?5Zh=Ga0a`HlU$$ErZF(kDdP__18^Bfo$85|j$7?>GA7FvLv#lXO_gF$!$ zSRP@9G}sI=1||ju1{SCpAeJ+O3j-sAE6ACQ49*M;3=o6e7=jrY+##-EU|=v|U<P}I zOKS@QJ0s&Z21#`D6~N|$0)&Bq3u->ZD3BXH800~2oZQ19h2$?)upWq;IH7t#f#}KL z#lXnm4G$kq22TbbgeyJauJmPKWbk9~XJ7<-9~3K~@a6_NavOuf4h9u;S86d>*TaqF zhFZzU5Wt`d3U&qt21f=aa1e2V%+TJ(poNGxF0h0i0|$dX4tEDK1R-1*2#(NT43q0& zM(d#)ZOp*IU_!jnAq*zi94)<#!4TbK3kD7bOQ^|apcH@{+88N-i6Io6c*5XmJDh<5 E0M2ok0{{R3 delta 1271 zcmdlcyhD`h)W2Q(7#J9A84@>gF)<1WX=wUnC6*=XmzHGa==)?AmrPb+3}7^yT*_$3 z0_JXDlwnkwe3ntR9>lfgV&G#?<Y7=^P-bLcP0laPD`8}S8<LZlm#*)VpO<dU#h}Wd z#?GM5!=S;S$;iM5wgWEB$iSJCpO+q%m{Xd{$iS?j>BY{V&B&mRP!*b2Tv||&UsRHs z;$M(jlvt9PAD!n~k(^pkl9`{!!Jx~?ARn5SnOl&Pnwy$elA5BBn_7~QpQ50imYJ90 zROy+b&c&d`pwGj=%3#3AAgTfJCD;YQB}JKe>DHQIAio&#Fc>qKOpa!9)HmZ{FlR7f zWMC^TNJ%V71<6|SFjz5IGcvHHq~@fSq%tzFXn1Odu`}2*GMG)C$ZR&5k@=;N3uYMh zvzTr+XW?gL%G8)F&#F2(mz4(+n*A)MlXtN4D~fA)AqTua*jtG?){G2{UW^Q$8eWsD zSv)7-XWg%C!@$VE#K6tK!@$U3$H2hA#=yv6&%nUI#lXnG3g$UXKFB7f?g*7*VBlqN zVqgHPXJBAuaAt5}U|?WmaAgo;U}A8X{Etnt9-&qQNi7Fdts4Ua0~3Qg10#b6gC_$s zg98Htg9X?X3=Awg7=$-~<q>8`g3S<PU}9ikV1b$eVtFxmGcYpvFfcH%GBS8EFfc$2 z_GRE?WblK!W<I+#l56C^CV_kk@*l+I5KSQ0_%p~uUE|Nd1h$P+YYPLr_BI9?kdv4h z5KdAC8w7C@C)`P#3;_&*42%px@ZbT7)CVJ6831=>2m>QSD1#;gBiQ?<3``7+3=9n1 zAPcuKC~RX;S&J458el6SZs&$t2@1h5sGIE=m>HNs@wS^mD^f^jJA>YShza%Zh|>cb z0dX}K)Cf>Ocrk<{oQMd>2nI%mNCp#7tTHe#IHHA#;YNt@AS=OvWyrw6V1#4?EL`dt z7&sZC7@`qYM1d1b3{Im>(Tz4^;9xMvVl)nuV;QWG932lzH{h6*-o{{oZnQN62ZId` UqcIW~6GI%>)A8_}lEA<K08vx68UO$Q diff --git a/target/classes/static/css/gyj_imt.css b/target/classes/static/css/gyj_imt.css index fae8405..4cc3cb8 100644 --- a/target/classes/static/css/gyj_imt.css +++ b/target/classes/static/css/gyj_imt.css @@ -7,6 +7,9 @@ #header_div{ color: white; background-color: rgb(179, 199, 71); + padding: 10px; + + } #tiny_text{ @@ -20,7 +23,7 @@ #content_div{ background-color: white; - + } #central_div{ padding: 10px; @@ -32,6 +35,15 @@ color: white; background-color: rgb(0, 32, 65); } +.favorite_color { + color: rgb(0, 184, 222); +} +.favorite_back { + background-color: rgb(0, 184, 222); +} +.favorite_outline { + border-color: rgb(0, 184, 222); +} .logo{ width:30px; @@ -148,4 +160,82 @@ .card-black h2 { color: white !important; } +/* Ajoutez ces styles pour améliorer l'interface */ +.navbar { + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + + +.alert-info { + background-color: #f8f9fa; + border-left: 4px solid #072262; + color: #072262; +} + +.nav-link { + font-weight: 500; +} + +.btn-outline-light:hover { + color: #072262 !important; +} +/* Dashboard */ +.card { + border-radius: 10px; + box-shadow: 0 2px 5px rgba(0,0,0,0.1); + transition: transform 0.3s; +} + +.card:hover { + transform: translateY(-3px); +} + +.card-header { + border-radius: 10px 10px 0 0 !important; +} + +/* Profil */ +dl.row dt { + font-weight: normal; + color: #6c757d; +} + +.list-group-item { + border-left: 3px solid transparent; + transition: all 0.3s; +} + +.list-group-item:hover { + border-left-color: #072262; + background-color: #f8f9fa; +} +/* Style cohérent pour l'interface anglaise */ +.navbar-brand { + font-weight: 600; +} + + +.btn-outline-danger { + border-color: #dc3545; + color: #dc3545; +} +/* Style pour la bannière de bienvenue */ +.welcome-banner { + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + transition: all 0.3s ease; +} + +.welcome-banner:hover { + transform: translateY(-2px); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); +} + +.welcome-banner i { + font-size: 1.5rem; +} +/* Style pour le nom utilisateur */ +.text-white { + font-size: 1rem; + vertical-align: middle; +} diff --git a/target/classes/templates/application/application-confirmation.html b/target/classes/templates/application/application-confirmation.html index 49c8ab4..aa8fd92 100644 --- a/target/classes/templates/application/application-confirmation.html +++ b/target/classes/templates/application/application-confirmation.html @@ -1,36 +1,130 @@ -<!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> -<head> - <meta charset="UTF-8"> - <title>Confirmation de Candidature</title> - <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> -</head> -<body> - <div class="container"> - <div class="alert alert-success mt-5 text-center"> - <h2> Votre candidature a été soumise avec succès !</h2> - </div> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Application Confirmation</title> +<section> + <head> + <meta charset="UTF-8"> + <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> + <style> + .confirmation-card { + border-radius: 12px; + box-shadow: 0 6px 15px rgba(0, 0, 0, 0.08); + border: none; + overflow: hidden; + } + .confirmation-header { + background:#00B8DEFF; + color: white; + padding: 1.8rem; + text-align: center; + } + .confirmation-icon { + font-size: 2.5rem; + margin-bottom: 1rem; + } + .detail-card { + border-left: 4px solid #00B8DEFF; + border-radius: 8px; + margin-bottom: 1.5rem; + } + .detail-item { + padding: 1rem 1.5rem; + border-bottom: 1px solid #f0f0f0; + } + .detail-item:last-child { + border-bottom: none; + } + .sector-badge { + background-color: #e8f0fe; + color: #00B8DEFF; + padding: 6px 12px; + border-radius: 18px; + margin-right: 8px; + margin-bottom: 8px; + display: inline-block; + font-size: 0.85rem; + } + .cv-link i { + margin-right: 8px; + } + .timestamp { + color: #5f6368; + font-size: 0.9rem; + } + </style> + </head> + <body> + <div class="container py-5"> + <div class="row justify-content-center"> + <div class="col-lg-8"> + <!-- Confirmation Card --> + <div class="card confirmation-card"> + <div class="confirmation-header"> + <div class="confirmation-icon"> + <i class="fas fa-check-circle"></i> + </div> + <h2>Application Submitted Successfully</h2> + <p class="mb-0">Thank you for your application</p> + </div> - <h3 class="mt-4">📄 Détails de la candidature :</h3> + <div class="card-body p-4"> + <!-- Application Details --> + <div class="card detail-card mb-4"> + <div class="card-body p-0"> + <div class="detail-item"> + <h5 class="text-muted mb-3">Application Details</h5> + <dl class="row"> + <dt class="col-sm-4">Application Number</dt> + <dd class="col-sm-8 font-weight-bold" th:text="${appId}"></dd> - <div th:if="${application != null}"> - <table class="table table-bordered"> - <tr><th>ID</th><td th:text="${application.id != null ? application.id : 'Non spécifié'}"></td></tr> - <tr><th> CV</th><td th:text="${application.cv != null ? application.cv : 'Non spécifié'}"></td></tr> - <tr><th> Niveau de qualification</th> - <td th:text="${application.qualificationlevel != null ? application.qualificationlevel.label : 'Non spécifié'}"></td> - </tr> - <tr><th> Secteurs d'activité</th> - <td th:each="sector : ${application.sectors}" th:text="${sector.label}"></td> - </tr> - <tr><th> Date de dépôt</th> - <td th:text="${application.appdate != null ? application.appdate : 'Non spécifié'}"></td> - </tr> - - </table> - </div> + <dt class="col-sm-4">Submission Date</dt> + <dd class="col-sm-8"> + <span th:text="${#temporals.format(appDate, 'dd MMMM yyyy - HH:mm')}" class="timestamp"></span> + </dd> + </dl> + </div> + </div> + </div> + + <!-- Candidate Information --> + <div class="card detail-card"> + <div class="card-body p-0"> + <div class="detail-item"> + <h5 class="text-muted mb-3">Your Information</h5> + <dl class="row"> + <dt class="col-sm-4">CV Document</dt> + <dd class="col-sm-8"> + <span th:text="${appCv} ?: 'Not specified'" + class="badge favorite_back text-white"></span> + </dd> + + <dt class="col-sm-4">Qualification Level</dt> + <dd class="col-sm-8"> + <span th:text="${qualification?.label} ?: 'Not specified'" + class="badge favorite_back text-white"></span> + </dd> - <a href="/" class="btn btn-primary mt-3"> Retour à l'accueil</a> + <dt class="col-sm-4">Activity Sectors</dt> + <dd class="col-sm-8"> + <div th:each="sector : ${sectors}"> + <span class="sector-badge" th:text="${sector.label}"></span> + </div> + <div th:if="${#lists.isEmpty(sectors)}" class="text-muted"> + No sectors specified + </div> + </dd> + </dl> + </div> + </div> + </div> + + </div> + </div> + </div> + </div> </div> -</body> -</html> + + <!-- Font Awesome for icons --> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> + </body> +</section> +</html> \ No newline at end of file diff --git a/target/classes/templates/application/application-details.html b/target/classes/templates/application/application-details.html index 4a53d5b..1f06033 100644 --- a/target/classes/templates/application/application-details.html +++ b/target/classes/templates/application/application-details.html @@ -1,49 +1,113 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> <head> - <meta charset="UTF-8"> - <title>Détails de la Candidature</title> - <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> + <title>Applications</title> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"> + <style> + :root { + --primary: #072262; + --accent: #1ABC9C; + --text-primary: #333333; + --text-secondary: #666666; + --bg-light: #F5F5F5; + --border-light: #E0E0E0; + } + + .favorite_color { + color: var(--primary); + } + + .favorite_back { + background-color: var(--accent); + color: white; + border: none; + } + + .favorite_outline { + border-color: var(--accent); + color: var(--accent); + } + + .favorite_outline:hover { + background-color: var(--accent); + color: white; + } + </style> </head> <body> - <div class="container mt-5"> - <h2 class="mb-4"> Détails de la Candidature</h2> +<section> + <header class="mb-4"> + <h2 class="fw-bold border-bottom pb-2">Application Details</h2> + </header> - <div th:if="${error}" class="alert alert-warning"> - <p th:text="${error}"></p> - </div> + <!-- Alert Messages --> + <div th:if="${error}" class="alert alert-warning alert-dismissible fade show" role="alert"> + <i class="bi bi-exclamation-triangle-fill me-2"></i> + <span th:text="${error}"></span> + <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> + </div> + + <div class="card shadow-sm mb-4"> + <div class="card-body"> + <div class="row g-3"> + <!-- Left Column --> + <div class="col-md-6"> + <h4 class="favorite_color fw-bold mb-3"> + <i class="bi bi-person-badge"></i> Candidate Information + </h4> - <div class="card"> - <div class="card-body"> - <h5 class="card-title"> - Nom: - <span th:if="${application.candidate != null}" th:text="${application.candidate.firstname + ' ' + application.candidate.lastname}"></span> - <span th:unless="${application.candidate != null}">Non disponible</span> - </h5> - - <p class="card-text"> - <span th:if="${application.cv != null}" th:text="'CV: ' + ${application.cv}"></span> - <span th:unless="${application.cv != null}">Non disponible</span> - </p> - - <p class="card-text"> - <span th:if="${application.qualificationlevel != null}" th:text="'Niveau de Qualification: ' + ${application.qualificationlevel.label}"></span> - <span th:unless="${application.qualificationlevel != null}">Non disponible</span> - </p> - - <p class="card-text"> - <span th:each="sector : ${application.sectors}" th:text="'Secteur: ' + ${sector.label}"></span> - <span th:if="${#lists.isEmpty(application.sectors)}">Secteurs non disponibles</span> - </p> - - <p class="card-text"> - <span th:if="${application.appdate != null}" th:text="'Date de dépôt: ' + ${application.appdate}"></span> - <span th:unless="${application.appdate != null}">Non disponible</span> - </p> + <div class="mb-3"> + <label class="form-label fw-semibold">Full Name</label> + <div class="form-control-plaintext bg-light p-2 rounded"> + <span th:text="${candidate.firstname + ' ' + candidate.lastname}"></span> + </div> + </div> + + <div class="mb-3"> + <label class="form-label fw-semibold">CV</label> + <div class="form-control-plaintext bg-light p-2 rounded"> + <span th:text="${cv}"></span> + </div> + </div> + <div class="mb-3"> + <label class="form-label fw-semibold">Application Date</label> + <div class="form-control-plaintext bg-light p-2 rounded"> + <span th:text="${#temporals.format(appdate, 'MMMM dd, yyyy')}"></span> + </div> + </div> + </div> + + <!-- Right Column --> + <div class="col-md-6"> + <h4 class="favorite_color fw-bold mb-3"> + <i class="bi bi-award"></i> Qualifications + </h4> + + <div class="mb-3"> + <label class="form-label fw-semibold">Qualification Level</label> + <div class="form-control-plaintext bg-light p-2 rounded"> + <span th:text="${qualification.label}"></span> + </div> + </div> + + <div class="mb-3"> + <label class="form-label fw-semibold">Sectors</label> + <div class="form-control-plaintext bg-light p-2 rounded"> + <span th:each="sector : ${sectors}" class="badge favorite_back me-1 mb-1" + th:text="${sector.label}"></span> + </div> + </div> + </div> </div> </div> - - <a th:href="@{/applications/list}" class="btn btn-primary mt-3">Retour à la liste des candidatures</a> </div> + + <footer class="d-flex justify-content-between mt-4"> + <a th:href="@{/applications/list}" class="btn btn-outline-secondary"> + <i class="bi bi-arrow-left"></i> Back to Applications List + </a> + </footer> +</section> </body> </html> \ No newline at end of file diff --git a/target/classes/templates/application/application-edit.html b/target/classes/templates/application/application-edit.html index f68c9ec..6a3bb68 100644 --- a/target/classes/templates/application/application-edit.html +++ b/target/classes/templates/application/application-edit.html @@ -1,17 +1,240 @@ -<form th:action="@{/applications/update}" method="post"> - <input type="hidden" name="id" th:value="${application.id}"> - <label>CV :</label> - <input type="text" name="cv" th:value="${application.cv}" required> - - <label>Niveau de qualification :</label> - <select name="qualificationLevel"> - <option th:each="q : ${qualifications}" th:value="${q.id}" th:text="${q.label}"></option> - </select> - - <label>Secteurs :</label> - <select name="sectors" multiple> - <option th:each="s : ${sectors}" th:value="${s.id}" th:text="${s.label}"></option> - </select> - - <button type="submit">Modifier</button> -</form> + <!DOCTYPE html> + <html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> + <title>Edit Job Application</title> + <section> + <head> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"> + <style> + .form-container { + max-width: 800px; + margin: 0 auto; + background-color: #fff; + border-radius: 8px; + padding: 2rem; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + } + .sector-item { + display: inline-block; + margin: 5px; + cursor: pointer; + padding: 5px 10px; + border-radius: 4px; + background: #f8f9fa; + border: 1px solid #dee2e6; + } + .sector-item.selected { + background: #00B8DEFF; + color: white; + border-color: #00B8DEFF; + } + #selectedSectors { + min-height: 40px; + border: 1px solid #ced4da; + border-radius: 4px; + padding: 8px; + margin-top: 5px; + } + .file-upload { + border: 2px dashed #dee2e6; + border-radius: 8px; + padding: 20px; + text-align: center; + cursor: pointer; + transition: all 0.3s; + } + .file-upload:hover { + border-color: #00B8DEFF; + background-color: #f8f9fa; + } + .file-upload i { + font-size: 2rem; + color: #6c757d; + } + .file-name { + margin-top: 10px; + font-weight: bold; + color: #00B8DEFF; + } + .btn-primary { + background-color: rgb(0, 184, 222); + border-color: rgb(0, 184, 222); + } + </style> + </head> + <body class="bg-light"> + + <div class="container py-4"> + <div class="form-container"> + <h2 class="text-center mb-4"> + Edit Job Application + </h2> + <form th:action="@{/applications/update}" method="post" class="needs-validation" novalidate> + <input type="hidden" name="id" th:value="${id}" /> + <!-- Improved CV Field --> + <div class="mb-3"> + <label class="form-label">Resume*</label> + <div class="file-upload" onclick="document.getElementById('cvFile').click()"> + <i class="bi bi-upload"></i> + <div id="cvText">Click to upload your resume</div> + <div id="fileName" class="file-name d-none"></div> + <input type="file" id="cvFile" class="d-none bold" accept=".pdf,.doc,.docx"> + <input type="hidden" id="cv" name="cv" th:text="${cv}" required> + </div> + <small class="text-muted">Accepted formats: PDF, DOC, DOCX</small> + <div class="invalid-feedback">Please upload your resume</div> + </div> + + <div class="col-md-6"> + <label class="form-label">Publication Date*</label> + <input type="date" class="form-control" name="publicationDate" required + th:value="${appDate != null} ? ${#temporals.format(appDate, 'yyyy-MM-dd')} : ${#temporals.format(#temporals.createToday(), 'yyyy-MM-dd')}"/> + </div> + + <!-- Qualification Level --> + <div class="col-md-6"> + <label class="form-label">Qualification Level*</label> + <select class="form-select" name="qualificationLevel" required> + <option value="">-- Select level --</option> + <option th:each="level : ${qualifications}" + th:value="${level.id}" + th:text="${level.label}" + th:selected="${qualApp?.id == level.id}"> + </option> + </select> + </div> + + <!-- Industry Sectors - Version corrigée --> + <div class="mb-3"> + <label class="form-label">Industry Sectors*</label> + + <!-- Liste complète des secteurs --> + <div class="mb-2"> + <div th:each="sector : ${sectors}" + class="sector-item" + th:data-id="${sector.id}" + th:classappend="${#lists.contains(sectorApp.![id], sector.id)} ? 'selected' : ''" + th:text="${sector.label}" + onclick="toggleSector(this)"></div> + </div> + + <!-- Champ affichant les secteurs sélectionnés --> + <div id="selectedSectorsDisplay" class="mb-2"> + <span th:if="${sectorApp.empty}">No sectors selected</span> + <th:block th:each="sector : ${sectorApp}"> + <div th:data-id="${sector.id}"> + <span th:text="${sector.label}"></span> + </div> + </th:block> + </div> + + <input type="hidden" id="sectorIds" name="sectorIds" + th:value="${#strings.listJoin(sectorApp.![id], ',')}" required> + <div class="invalid-feedback">Please select at least one sector</div> + </div> + + <!-- Buttons --> + <div class="d-flex justify-content-between mt-4"> + <a th:href="@{/applications/list}" class="btn btn-outline-secondary"> + <i class="bi bi-arrow-left me-1"></i> Cancel + </a> + <button type="submit" class="btn btn-primary"> + <i class="bi bi-save me-1"></i> Save Changes + </button> + </div> + </form> + </div> + </div> + + <script> + // Initialize with already selected sectors from the application object + const selectedSectors = new Set( + document.getElementById('sectorIds').value.split(',').filter(Boolean) + ); + + // Mark selected sectors on page load + document.addEventListener('DOMContentLoaded', function() { + updateSelectedDisplay(); + + // Mark initially selected sectors in the list + selectedSectors.forEach(sectorId => { + const element = document.querySelector(`.sector-item[data-id="${sectorId}"]`); + if (element) { + element.classList.add('selected'); + } + }); + }); + + function toggleSector(element) { + const sectorId = element.getAttribute('data-id'); + const sectorLabel = element.textContent; + + if (selectedSectors.has(sectorId)) { + selectedSectors.delete(sectorId); + element.classList.remove('selected'); + } else { + selectedSectors.add(sectorId); + element.classList.add('selected'); + } + + updateSelectedDisplay(); + } + + function updateSelectedDisplay() { + const displayDiv = document.getElementById('selectedSectorsDisplay'); + const hiddenInput = document.getElementById('sectorIds'); + + // Clear the display + displayDiv.innerHTML = ''; + + if (selectedSectors.size === 0) { + displayDiv.innerHTML = '<span>No sectors selected</span>'; + } else { + // Add selected sectors as tags + selectedSectors.forEach(sectorId => { + const sectorElement = document.querySelector(`.sector-item[data-id="${sectorId}"]`); + if (sectorElement) { + const tag = document.createElement('div'); + tag.setAttribute('data-id', sectorId); + tag.innerHTML = `<span>${sectorElement.textContent}</span>`; + displayDiv.appendChild(tag); + } + }); + } + + // Update hidden input with selected sector IDs + hiddenInput.value = Array.from(selectedSectors).join(','); + } + + + + // File upload handling + document.getElementById('cvFile').addEventListener('change', function(e) { + const file = e.target.files[0]; + if (file) { + document.getElementById('cvText').classList.add('d-none'); + document.getElementById('fileName').textContent = file.name; + document.getElementById('fileName').classList.remove('d-none'); + document.getElementById('cv').value = file.name; + } + }); + + // Form validation + (function() { + 'use strict'; + const forms = document.querySelectorAll('.needs-validation'); + + Array.from(forms).forEach(form => { + form.addEventListener('submit', event => { + if (!form.checkValidity()) { + event.preventDefault(); + event.stopPropagation(); + } + form.classList.add('was-validated'); + }, false); + }); + })(); + </script> + </body> + </section> + </html> \ No newline at end of file diff --git a/target/classes/templates/application/application-list.html b/target/classes/templates/application/application-list.html index 2200f31..2bdac21 100644 --- a/target/classes/templates/application/application-list.html +++ b/target/classes/templates/application/application-list.html @@ -1,75 +1,133 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> -<head> - <meta charset="UTF-8"> - <title>Liste des Candidatures</title> - <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> -</head> -<body> - <div class="container mt-5"> - <h2 class="mb-4">Liste des Candidatures</h2> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Applications</title> +<section> + <head> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"> + <style> + .application-card { + transition: all 0.3s ease; + border-left: 4px solid transparent; + } + .application-card:hover { + transform: translateY(-2px); + border-left-color: #00b8de; + background-color: #f8f9fa; + } + .table-container { + border-radius: 8px; + overflow: hidden; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); + } + .sector-badge { + margin-right: 4px; + margin-bottom: 4px; + } + .empty-state-icon { + font-size: 3rem; + opacity: 0.5; + } + </style> + </head> + <body class="bg-light"> - <div th:if="${error}" class="alert alert-warning"> - <p th:text="${error}"></p> + <div class="container py-4"> + <!-- Header Section --> + <div class="d-flex justify-content-between align-items-center mb-4"> + <h2 class="fw-bold text-black mb-0"> + <i class="bi bi-file-earmark-text me-2"></i> Job Applications + </h2> </div> - <table class="table table-bordered table-striped" th:if="${applicationsList}"> - <thead> - <tr> - <th>ID</th> - <th>Nom</th> - <th>Prénom</th> - <th>CV</th> - <th>Niveau de Qualification</th> - <th>Secteurs d'activité</th> - <th>Date de dépôt</th> - <th>Détails</th> - <th>Modifier</th> - <th>Supprimer</th> - </tr> - </thead> - <tbody> - <tr th:each="app : ${applicationsList}"> - <td th:text="${app.id}"></td> - <td th:text="${app.candidate.firstname}"></td> - <td th:text="${app.candidate.lastname}"></td> - <td><a th:href="@{${app.cv}}" th:text="${app.cv}"></a></td> - <td th:text="${app.qualificationlevel.label}"></td> - <td> - <ul> - <li th:each="sector : ${app.sectors}" th:text="${sector.label}"></li> - </ul> - </td> - <td th:text="${app.appdate}"></td> - <td> - <a th:href="@{/applications/details/{id}(id=${app.id})}" class="btn btn-info">Détails</a> - </td> - <!-- Vérification que l'utilisateur est connecté et est le propriétaire --> - <td th:if="${session.uid != null && session.uid == app.candidate.id}"> - <a th:href="@{/applications/edit/{id}(id=${app.id})}" class="btn btn-warning">Modifier</a> - </td> - <td th:if="${session.uid != null && session.uid == app.candidate.id}"> - <a th:href="@{/applications/delete/{id}(id=${app.id})}" class="btn btn-danger" - onclick="return confirm('Êtes-vous sûr de vouloir supprimer cette candidature ?');"> - Supprimer - </a> - </td> - <!-- Affichage d'un message si l'utilisateur n'est pas connecté --> - <td th:if="${session.uid == null}" colspan="2"> - <a th:href="@{/login}" class="btn btn-secondary">Se connecter</a> - </td> - </tr> - </tbody> - </table> + <!-- Applications Table --> + <div class="card shadow-sm table-container"> + <div class="card-body p-0"> + <div class="table-responsive"> + <table class="table table-hover align-middle mb-0" th:if="${not #lists.isEmpty(applicationsList)}"> + <thead class="table-light"> + <tr> + <th class="ps-4">First Name</th> + <th>Last Name</th> + <th>Qualification</th> + <th>Sectors</th> + <th>Applied Date</th> + <th class="text-end pe-4">Actions</th> + </tr> + </thead> + <tbody> + <tr th:each="app : ${applicationsList}" class="application-card"> + <td class="ps-4 fw-semibold" th:text="${app.candidate.firstname}"></td> + <td th:text="${app.candidate.lastname}"></td> + <td th:text="${app.qualificationlevel?.label} ?: 'N/A'"></td> + <td> + <div class="d-flex flex-wrap"> + <span th:each="sector : ${app.sectors}" + class="badge favorite_back sector-badge" + th:text="${sector.label}"></span> + </div> + </td> + <td th:text="${#temporals.format(app.appdate, 'yyyy-MM-dd')}"></td> + <td class="text-end pe-4"> + <div class="d-flex gap-2 justify-content-end"> + <a th:href="@{'/applications/details/' + ${app.id}}" + class="btn btn-sm favorite_outline"> + <i class="bi bi-eye"></i> View + </a> + <th:block th:if="${session != null and session.userType == 'Candidate' and session.loggedInUser.id == app.candidate.id}"> + <a th:href="@{'/applications/edit/' + ${app.id}}" + class="btn btn-sm btn-outline-warning"> + <i class="bi bi-pencil"></i> Edit + </a> + <a th:href="@{'/applications/delete/' + ${app.id}}" + class="btn btn-sm btn-outline-danger" + onclick="return confirm('Are you sure you want to delete this application?');"> + <i class="bi bi-trash"></i> Delete + </a> + </th:block> + </div> + </td> + </tr> + </tbody> + </table> - <a th:href="@{/}" class="btn btn-primary mt-3">Retour à l'accueil</a> + <!-- Empty State --> + <div th:if="${#lists.isEmpty(applicationsList)}" class="text-center py-5"> + <i class="bi bi-file-earmark-x empty-state-icon text-muted"></i> + <h4 class="mt-3 text-muted">No applications found</h4> + </div> + </div> + </div> + </div> </div> -</body> + + <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> + </body> +</section> </html> +<!-- +</td> +<td> + <a th:href="@{/applications/details/{id}(id=${app.id})}" class="btn btn-info">Détails</a> +</td> + +<td th:if="${session.uid != null && session.uid == app.candidate.id}"> + <a th:href="@{/applications/edit/{id}(id=${app.id})}" class="btn btn-warning">Modifier</a> +</td> +<td th:if="${session.uid != null && session.uid == app.candidate.id}"> + <a th:href="@{/applications/delete/{id}(id=${app.id})}" class="btn btn-danger" + onclick="return confirm('Êtes-vous sûr de vouloir supprimer cette candidature ?');"> + Supprimer + </a> +</td> +<td th:if="${session.uid == null}" colspan="2"> + <a th:href="@{/login}" class="btn btn-secondary">Se connecter</a> +</td> +!--> diff --git a/target/classes/templates/application/application-update-form.html b/target/classes/templates/application/application-update-form.html index 26266c1..3eefb8c 100644 --- a/target/classes/templates/application/application-update-form.html +++ b/target/classes/templates/application/application-update-form.html @@ -1,6 +1,8 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> -<head> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Applications</title> +<section> + <head> <meta charset="UTF-8"> <title>Mettre à jour la Candidature</title> <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> @@ -12,7 +14,7 @@ <form th:action="@{/applications/update/{id}(id=${application.id})}" method="post"> <div class="form-group"> <label for="cv">CV</label> - <input type="text" id="cv" name="cv" class="form-control" th:value="${application.cv}" required> + <input type="text" id="cv" name="cvPath" class="form-control" th:value="${application.cv}" required> </div> <div class="form-group"> @@ -37,10 +39,11 @@ </select> </div> - <button type="submit" class="btn btn-primary mt-3">Mettre à jour</button> + <button type="submit" class="btn favorite_back mt-3">Mettre à jour</button> </form> <a th:href="@{/applications/list}" class="btn btn-secondary mt-3">Retour à la liste des candidatures</a> </div> </body> +</section> </html> diff --git a/target/classes/templates/application/apply.html b/target/classes/templates/application/apply.html index 4615526..66f297c 100644 --- a/target/classes/templates/application/apply.html +++ b/target/classes/templates/application/apply.html @@ -1,74 +1,192 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> -<head> - <meta charset="UTF-8"> - <title>Postuler à une offre</title> - <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> - <style> - body { - background-color: #f8f9fa; /* Couleur de fond */ - } - .container { - max-width: 600px; - margin-top: 50px; - background: #ffffff; - padding: 20px; - border-radius: 10px; - box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1); - } - h2 { - text-align: center; - color: #007bff; /* Bleu Bootstrap */ - } - label { - font-weight: bold; - color: #343a40; - } - .form-control { - margin-bottom: 10px; - } - .btn-submit { - background-color: #007bff; - color: white; - width: 100%; - } - .btn-submit:hover { - background-color: #0056b3; - } - </style> -</head> -<body> - <div class="container"> - <h2> Postuler à une offre</h2> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Job Application</title> +<section> + <head> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"> + <style> + .form-container { + max-width: 800px; + margin: 0 auto; + background-color: #fff; + border-radius: 8px; + padding: 2rem; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + } + .sector-item { + display: inline-block; + margin: 5px; + cursor: pointer; + padding: 5px 10px; + border-radius: 4px; + background: #f8f9fa; + border: 1px solid #dee2e6; + } + .sector-item.selected { + background: #0d6efd; + color: white; + border-color: #0d6efd; + } + #selectedSectors { + min-height: 40px; + border: 1px solid #ced4da; + border-radius: 4px; + padding: 8px; + margin-top: 5px; + } + .file-upload { + border: 2px dashed #dee2e6; + border-radius: 8px; + padding: 20px; + text-align: center; + cursor: pointer; + transition: all 0.3s; + } + .file-upload:hover { + border-color: #0d6efd; + background-color: #f8f9fa; + } + .file-upload i { + font-size: 2rem; + color: #6c757d; + } + .file-name { + margin-top: 10px; + font-weight: bold; + color: #0d6efd; + } + </style> + </head> + <body class="bg-light"> + + <div class="container py-4"> + <div class="form-container"> + <h2 class="text-center mb-4"> + <i class="bi bi-person-plus me-2"></i> + Apply for Job + </h2> - <form th:action="@{/applications/apply}" method="post"> - <!-- Champ CV --> - <div class="form-group"> - <label for="cv">CV :</label> - <input type="text" id="cv" name="cv" class="form-control" placeholder="Lien vers votre CV" required> - </div> + <form th:action="@{/applications/apply}" method="post" class="needs-validation" novalidate> + <!-- Improved CV Field --> + <div class="mb-3"> + <label class="form-label">Resume*</label> + <div class="file-upload" onclick="document.getElementById('cvFile').click()"> + <i class="bi bi-upload"></i> + <div id="cvText">Click to upload your resume</div> + <div id="fileName" class="file-name d-none"></div> + <input type="file" id="cvFile" class="d-none" accept=".pdf,.doc,.docx"> + <input type="hidden" id="cv" name="cv" required> + </div> + <small class="text-muted">Accepted formats: PDF, DOC, DOCX</small> + <div class="invalid-feedback">Please upload your resume</div> + </div> - <!-- Niveau de qualification --> - <div class="form-group"> - <label for="qualificationLevel">Niveau de qualification :</label> - <select id="qualificationLevel" name="qualificationLevel" class="form-control" required> - <option value="">Choisir un niveau</option> - <option th:each="qualification : ${qualifications}" th:value="${qualification.id}" th:text="${qualification.label}"></option> - </select> - </div> + <!-- Qualification Level --> + <div class="mb-3"> + <label class="form-label">Qualification Level*</label> + <select class="form-select" name="qualificationLevel" required> + <option value="">-- Select level --</option> + <option th:each="qualification : ${qualifications}" + th:value="${qualification.id}" + th:text="${qualification.label}"></option> + </select> + </div> + <div class="col-md-6"> + <label class="form-label">Publication Date*</label> + <input type="date" class="form-control" name="publicationDate" required + th:value="${application.appdate != null} ? ${#temporals.format(jobOffer.appdate, 'yyyy-MM-dd')} : ${#temporals.format(#temporals.createToday(), 'yyyy-MM-dd')}"/> + </div> - <!-- Secteurs d'activité --> - <div class="form-group"> - <label for="sectors">Secteurs d'activité :</label> - <select id="sectors" name="sectors" class="form-control" multiple required> - <option th:each="sector : ${sectors}" th:value="${sector.id}" th:text="${sector.label}"></option> - </select> - <small class="form-text text-muted">Maintenez la touche Ctrl (Cmd sur Mac) pour sélectionner plusieurs options.</small> - </div> + <!-- Improved Industry Sectors Selection --> + <div class="mb-3"> + <label class="form-label">Industry Sectors*</label> + <div class="mb-2"> + <div th:each="sector : ${sectors}" + class="sector-item" + th:data-id="${sector.id}" + th:text="${sector.label}" + onclick="toggleSector(this)"></div> + </div> + <div id="selectedSectors" class="mb-2">No sectors selected</div> + <input type="hidden" id="sectorIds" name="sectors" required> + <div class="invalid-feedback">Please select at least one sector</div> + </div> - <!-- Bouton de soumission --> - <button type="submit" class="btn btn-submit"> Soumettre</button> - </form> + <!-- Buttons --> + <div class="d-flex justify-content-between mt-4"> + <a th:href="@{/jobs}" class="btn btn-outline-secondary"> + <i class="bi bi-arrow-left me-1"></i> Cancel + </a> + <button type="submit" class="btn favorite_back"> + <i class="bi bi-send me-1"></i> Submit Application + </button> + </div> + </form> + </div> </div> -</body> -</html> + + <script> + // Sector selection management + const selectedSectors = new Set(); + + function toggleSector(element) { + const sectorId = element.getAttribute('data-id'); + + if (selectedSectors.has(sectorId)) { + selectedSectors.delete(sectorId); + element.classList.remove('selected'); + } else { + selectedSectors.add(sectorId); + element.classList.add('selected'); + } + + updateSelectedDisplay(); + } + + function updateSelectedDisplay() { + const displayDiv = document.getElementById('selectedSectors'); + const hiddenInput = document.getElementById('sectorIds'); + + if (selectedSectors.size === 0) { + displayDiv.textContent = 'No sectors selected'; + hiddenInput.value = ''; + } else { + displayDiv.textContent = Array.from(selectedSectors).map(id => { + return document.querySelector(`.sector-item[data-id="${id}"]`).textContent; + }).join(', '); + + hiddenInput.value = Array.from(selectedSectors).join(','); + } + } + + // File upload handling + document.getElementById('cvFile').addEventListener('change', function(e) { + const file = e.target.files[0]; + if (file) { + document.getElementById('cvText').classList.add('d-none'); + document.getElementById('fileName').textContent = file.name; + document.getElementById('fileName').classList.remove('d-none'); + document.getElementById('cv').value = file.name; + } + }); + + // Form validation + (function() { + 'use strict'; + const forms = document.querySelectorAll('.needs-validation'); + + Array.from(forms).forEach(form => { + form.addEventListener('submit', event => { + if (!form.checkValidity()) { + event.preventDefault(); + event.stopPropagation(); + } + form.classList.add('was-validated'); + }, false); + }); + })(); + </script> + </body> +</section> +</html> \ No newline at end of file diff --git a/target/classes/templates/baseTemplate/base.html b/target/classes/templates/baseTemplate/base.html index f480ca2..264b500 100644 --- a/target/classes/templates/baseTemplate/base.html +++ b/target/classes/templates/baseTemplate/base.html @@ -7,44 +7,64 @@ <link th:href="@{/css/bootstrap.min.css}" rel="stylesheet"> <link th:href="@{/css/gyj_imt.css}" rel="stylesheet"> <link th:href="@{/css/bootstrap-icons.min.css}" rel="stylesheet"> - <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script> - + <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script> <script th:src="@{/js/bootstrap.bundle.min.js}"></script> <script th:src="@{/js/gyj_imt.js}"></script> <script src="http://localhost:35729/livereload.js"></script> - <link rel="icon" th:href="@{/img/favicon-32x32.png}" sizes="32x32" type="image/png"> <title th:replace="${title}">IMT Atlantique: Get Your Job</title> + <style> + html, body { + height: 100%; + } + + body { + display: flex; + flex-direction: column; + } + + main { + flex: 1 0 auto; + padding-bottom: 2rem; /* Espace avant le footer */ + } + footer { + flex-shrink: 0; + background-color: #f8f9fa; + padding: 1rem 0; + } + </style> </head> -<body> - <nav th:insert="~{/baseTemplate/nav :: fheader}" /> - <main class="container"> - <th:block th:replace="${content}"> - Empty Page - </th:block> - <div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true"> - <div class="modal-dialog" role="document"> - <div class="modal-content"> - <div class="modal-header"> - <h5 class="modal-title" id="exampleModalLabel">Confirmation of deletion</h5> - <button type="button" class="close" data-dismiss="modal" aria-label="Close"> - <span aria-hidden="true">×</span> - </button> - </div> - <div class="modal-body"> - <p class="alert alert-danger">Are you sure you want to <span id="delRecord"> </span> ?</p> - </div> - <div class="modal-footer"> - <button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button> - <a href="" class="btn btn-danger" id="delRef">Delete</a> - </div> +<body class="d-flex flex-column min-vh-100"> +<nav th:insert="~{/baseTemplate/nav :: fheader}" /> + +<main class="container flex-grow-1"> + <th:block th:replace="${content}"> + Empty Page + </th:block> + <div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title" id="exampleModalLabel">Confirmation of deletion</h5> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + </div> + <div class="modal-body"> + <p class="alert alert-danger">Are you sure you want to <span id="delRecord"> </span> ?</p> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button> + <a href="" class="btn btn-danger" id="delRef">Delete</a> </div> </div> </div> - </main> - <footer class="mt-4" th:replace="~{/baseTemplate/footer:: footer}" /> + </div> +</main> + +<footer class="mt-auto py-3 bg-light" th:replace="~{/baseTemplate/footer:: footer}" /> </body> </html> \ No newline at end of file diff --git a/target/classes/templates/baseTemplate/footer.html b/target/classes/templates/baseTemplate/footer.html index bfe41e5..a6172cc 100644 --- a/target/classes/templates/baseTemplate/footer.html +++ b/target/classes/templates/baseTemplate/footer.html @@ -1,5 +1,5 @@ <footer th:fragment="ffooter"> - <div id="footer_div" class="row"> + <div id="footer_div" class="row"> <div class="col"> IMT Atlantique - FIP </div> diff --git a/target/classes/templates/baseTemplate/head.html b/target/classes/templates/baseTemplate/head.html index 28f9bec..e60a2d4 100644 --- a/target/classes/templates/baseTemplate/head.html +++ b/target/classes/templates/baseTemplate/head.html @@ -18,4 +18,6 @@ <title>IMT Atlantique: Get Your Job</title> </head> <body> + </body> + </html> </div> \ No newline at end of file diff --git a/target/classes/templates/baseTemplate/nav.html b/target/classes/templates/baseTemplate/nav.html index 3653265..08e5b97 100644 --- a/target/classes/templates/baseTemplate/nav.html +++ b/target/classes/templates/baseTemplate/nav.html @@ -1,31 +1,20 @@ -//header.html <div th:fragment="fheader"> <div id="header_div" class="row h-10"> <div class="col-2 align-self-start"> <img th:src="@{/img/logo_imt.png}" /> </div> - <div class="col-6 align-self-center"></div> - <div class="col-2 align-self-end"> - <p th:if="${#ctx.session.uid} != null"> - <i class="bi bi-" style="font-size: 2rem; color: white;" th:title="${#ctx.session.usertype}" - th:attrappend="class=${#ctx.session.usertype=='company'?'buildings':'person'}"></i> - <span th:text="${#ctx.session.user.mail}" class="tiny_text" /> - </p> - </div> - - <div class="col-2 align-self-end"> - <th:block th:if="${#ctx.session.uid} != null"> - <a th:if="${#ctx.session.hasMessages == true}" th:href="@{/messages}" title="access to your message"><i - class="bi bi-envelope-at" style="font-size: 2rem; color: white;"></i></a> - <a href="/logout" title="logout from the webapp"><i class="bi bi-box-arrow-in-up" - style="font-size: 2rem; color: white;"></i></a> - </th:block> - <a th:if="${#ctx.session.uid} == null" title="Click to login" href="/login"> - <i class="bi bi-box-arrow-in-down" style="font-size: 2rem; color: white;" - title="login to access your data"></i> + <div class="col-8"></div> + <div class="col-2 text-end "> + <!-- Bouton de connexion/déconnexion seulement --> + <div th:if="${session.loggedInUser != null}"> + <a href="/logout" class="btn text-white fw-bold "> + <i class="bi bi-box-arrow-right"></i> Logout + </a> + </div> + <a th:unless="${session.loggedInUser != null}" href="/login" class="btn text-white fw-bold"> + <i class="bi bi-box-arrow-right"></i> Login </a> </div> - </div> <nav class="navbar navbar-expand-md navbar-light"> <div class="container-fluid"> @@ -41,24 +30,22 @@ <a class="nav-link" th:href="@{/companies}">Companies</a> </li> <li class="nav-item"> - <a class="nav-link" th:href="@{/jobs}">Jobs</a> + <a class="nav-link" th:href="@{/candidates/list}">Candidates</a> </li> <li class="nav-item"> - <a class="nav-link" th:href="@{/candidates/list}">Candidates</a> + <a class="nav-link" th:href="@{/jobs}">Job Offers</a> </li> <li class="nav-item"> <a class="nav-link" th:href="@{/applications/list}">Applications</a> </li> <li class="nav-item"> - <a class="nav-link" th:href="@{/qualificationLevels}">Qualification levels</a> + <a class="nav-link" th:href="@{/sectors}">Sectors</a> </li> <li class="nav-item"> - <a class="nav-link" th:href="@{/sectors}">Sectors</a> + <a class="nav-link" th:href="@{/qualificationLevels}">Qualification Levels</a> </li> </ul> - </div> </div> </nav> - </div> \ No newline at end of file diff --git a/target/classes/templates/candidate/candidates-list.html b/target/classes/templates/candidate/candidates-list.html index 039447c..283fdde 100644 --- a/target/classes/templates/candidate/candidates-list.html +++ b/target/classes/templates/candidate/candidates-list.html @@ -1,44 +1,106 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> -<head> - <meta charset="UTF-8"> - <title>Liste des Candidats</title> - <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> -</head> -<body> - <div class="container"> - <h2 class="mt-5">Liste des Candidats</h2> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Candidates</title> +<section> + <head> + <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"> + <style> + .candidate-card { + transition: all 0.3s ease; + border-left: 4px solid transparent; + } + .candidate-card:hover { + transform: translateY(-2px); + border-left-color: #3a7bd5; + background-color: #f8f9fa; + } + .table-container { + border-radius: 8px; + overflow: hidden; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); + } + .actions-cell { + white-space: nowrap; + } + .empty-state-icon { + font-size: 3rem; + opacity: 0.5; + } + </style> + </head> + <body class="bg-light"> - <!-- Message d'erreur --> - <div th:if="${error}" class="alert alert-danger" th:text="${error}"></div> + <div class="container py-4"> + <!-- Header Section --> + <div class="d-flex justify-content-between align-items-center mb-4"> + <h2 class="fw-bold text-black mb-0"> + <i class="bi bi-people me-2"></i> Candidates List + </h2> - <table class="table table-striped mt-3"> - <thead> - <tr> - <th>ID</th> - <th>Nom</th> - <th>Prénom</th> - <th>Ville</th> - <th>Email</th> - <th>Actions</th> - </tr> - </thead> - <tbody> - <tr th:each="candidate : ${candidates}"> - <td th:text="${candidate.id}"></td> - <td th:text="${candidate.lastname}"></td> - <td th:text="${candidate.firstname}"></td> - <td th:text="${candidate.city}"></td> - <td th:text="${candidate.mail}"></td> - <td> - <a th:href="@{/candidates/details/{id}(id=${candidate.id})}" class="btn btn-info btn-sm">Détails</a> - <span th:if="${#ctx.session.uid} != null and ${#ctx.session.uid} == ${candidate.id} ">PEUT SUPPRIMER</span> - </td> - </tr> - </tbody> - </table> + </div> - <a th:href="@{/}" class="btn btn-secondary mt-3">Retour</a> + + <!-- Candidates Table --> + <div class="card shadow-sm table-container"> + <div class="card-body p-0"> + <div class="table-responsive"> + <table class="table table-hover align-middle mb-0" th:if="${not #lists.isEmpty(candidates)}"> + <thead class="table-light"> + <tr> + <th class="ps-4">Last Name</th> + <th>First Name</th> + <th>City</th> + <th>Applications Submitted</th> + <th class="text-end pe-4">Actions</th> + </tr> + </thead> + <tbody> + <tr th:each="candidate : ${candidates}" class="candidate-card"> + <td class="ps-4 fw-semibold" th:text="${candidate.lastname}"></td> + <td th:text="${candidate.firstname}"></td> + <td> + <i class="bi bi-geo-alt text-secondary me-1"></i> + <span th:text="${candidate.city}"></span> + </td> + <td> + <span class="badge favorite_back rounded-pill" + th:text="${candidate.getCandidatAppCount()}"></span> + </td> + <td class="text-end pe-4 actions-cell"> + <div > + <a th:href="@{/candidates/details/{id}(id=${candidate.id})}" + class="btn btn-sm favorite_outline" + aria-label="View candidate details"> + <i class="bi bi-eye"></i> View + </a> + <th:block th:if="${session != null and session.userType == 'Candidate' and session.loggedInUser.id == candidate.id}"> + <a th:href="@{'/candidates/edit/' + ${candidate.id}}" + class="btn btn-sm btn-outline-warning"> + <i class="bi bi-pencil"></i> Edit + </a> + <a th:href="@{'/candidates/delete/' + ${candidate.id}}" + class="btn btn-sm btn-outline-danger" + onclick="return confirm('Are you sure you want to delete this candidate?');"> + <i class="bi bi-trash"></i> Delete + </a> + </th:block> + </div> + </td> + </tr> + </tbody> + </table> + <!-- Empty State --> + <div th:if="${#lists.isEmpty(candidates)}" class="text-center py-5"> + <i class="bi bi bi-people empty-state-icon text-muted"></i> + <h4 class="mt-3 text-muted">No candidates registered</h4> + </div> + </div> + </div> + </div> </div> -</body> + + <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> + </body> +</section> </html> diff --git a/target/classes/templates/candidate/confirmation.html b/target/classes/templates/candidate/confirmation.html index 8a22d72..bd630bf 100644 --- a/target/classes/templates/candidate/confirmation.html +++ b/target/classes/templates/candidate/confirmation.html @@ -1,34 +1,97 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> -<head> - <meta charset="UTF-8"> - <title>Confirmation</title> - <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> -</head> -<body> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Confirmation</title> +<section> + <head> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"> + <style> + .confirmation-card { + max-width: 800px; + margin: 2rem auto; + border-radius: 12px; + overflow: hidden; + box-shadow: 0 4px 20px rgba(0,0,0,0.08); + border: none; + } + .confirmation-header { + background: rgb(0, 184, 222); + color: white; + padding: 2rem; + text-align: center; + } + .confirmation-icon { + font-size: 3.5rem; + margin-bottom: 1rem; + } + .info-card { + border-left: 4px solid #00B8DEFF; + background-color: #f8f9fa; + } + .btn-custom { + background: rgb(0, 184, 222); + border: none; + padding: 10px 25px; + font-weight: 500; + transition: all 0.3s; + } + .btn-custom:hover { + transform: translateY(-2px); + box-shadow: 0 4px 15px rgba(79, 172, 254, 0.4); + } + </style> + </head> + <body class="bg-light"> <div class="container"> - <div class="alert alert-success mt-5 text-center"> - <h2>Operation Successful</h2> - <p th:text="${message}"></p> - </div> + <div class="card confirmation-card"> + <div class="confirmation-header"> + <i class="bi bi-check-circle-fill confirmation-icon"></i> + <h2 class="mb-3">Opération réussie</h2> + <p class="lead mb-0" th:text="${message}"></p> + </div> - <!-- Affichage des infos du candidat uniquement si c'est une modification --> - <div th:if="${candidate != null}"> - <h3>Informations mises à jour :</h3> - <table class="table table-bordered"> - <tr><th>ID</th><td th:text="${candidate.id}"></td></tr> - <tr><th>Nom</th><td th:text="${candidate.lastname}"></td></tr> - <tr><th>Prénom</th><td th:text="${candidate.firstname}"></td></tr> - <tr><th>Email</th><td th:text="${candidate.mail}"></td></tr> - <tr><th>Ville</th><td th:text="${candidate.city}"></td></tr> - </table> - </div> + <div class="card-body p-4"> + <!-- Affichage des infos du candidat --> + <div th:if="${candidate != null}" class="mb-4"> + <h4 class="mb-3 text-primary"> + <i class="bi bi-person-badge me-2"></i>Informations mises à jour + </h4> + + <div class="row"> + <div class="col-md-6 mb-3"> + <div class="p-3 info-card"> + <h6 class="text-muted">Identité</h6> + <p class="mb-1"><strong th:text="${candidate.firstname + ' ' + candidate.lastname}"></strong></p> + <small class="text-muted" th:text="'ID: ' + ${candidate.id}"></small> + </div> + </div> + + <div class="col-md-6 mb-3"> + <div class="p-3 info-card"> + <h6 class="text-muted">Coordonnées</h6> + <p class="mb-1" th:text="${candidate.mail}"></p> + <p class="mb-0" th:text="${candidate.city}"></p> + </div> + </div> + </div> + </div> - <!-- Bouton différent selon la situation --> - <div class="text-center"> - <a th:if="${candidate != null}" th:href="@{/candidates/list}" class="btn btn-primary">Retour à la liste des candidats</a> - <a th:if="${candidate == null}" th:href="@{/login}" class="btn btn-primary">Go to Login</a> + <!-- Bouton d'action --> + <div class="text-center mt-4"> + <a th:if="${candidate != null}" + th:href="@{/candidates/list}" + class="btn btn-custom text-white"> + <i class="bi bi-arrow-left me-2"></i>Retour à la liste + </a> + <a th:if="${candidate == null}" + th:href="@{/login}" + class="btn btn-custom text-white"> + <i class="bi bi-box-arrow-in-right me-2"></i>Se connecter + </a> + </div> + </div> </div> </div> -</body> -</html> + </body> +</section> +</html> \ No newline at end of file diff --git a/target/classes/templates/candidate/confirmationSupp.html b/target/classes/templates/candidate/confirmationSupp.html index a8ebbf0..9563393 100644 --- a/target/classes/templates/candidate/confirmationSupp.html +++ b/target/classes/templates/candidate/confirmationSupp.html @@ -12,7 +12,7 @@ <p th:text="${message}"></p> </div> - <a th:href="@{/candidates/list}" class="btn btn-primary">Retour à la liste des candidats</a> + <a th:href="@{/candidates/list}" class="btn favorite_back">Retour à la liste des candidats</a> </div> </body> </html> diff --git a/target/classes/templates/candidate/details.html b/target/classes/templates/candidate/details.html index 7e2ea97..9e7c594 100644 --- a/target/classes/templates/candidate/details.html +++ b/target/classes/templates/candidate/details.html @@ -1,25 +1,114 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> -<head> - <meta charset="UTF-8"> - <title>Détails du Candidat</title> - <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> -</head> -<body> - <div class="container"> - <h2 class="mt-5">Détails du Candidat</h2> - - <table class="table table-bordered mt-3"> - <tr><th>ID</th><td th:text="${candidate.id}"></td></tr> - <tr><th>Nom</th><td th:text="${candidate.lastname}"></td></tr> - <tr><th>Prénom</th><td th:text="${candidate.firstname}"></td></tr> - <tr><th>Email</th><td th:text="${candidate.mail}"></td></tr> - <tr><th>Ville</th><td th:text="${candidate.city}"></td></tr> - </table> - - <a th:href="@{/candidates/edit/{id}(id=${candidate.id})}" class="btn btn-warning">Modifier</a> - <a th:href="@{/candidates/delete/{id}(id=${candidate.id})}" class="btn btn-danger" onclick="return confirm('Voulez-vous vraiment supprimer ce candidat ?')">Supprimer</a> - <a th:href="@{/candidates/list}" class="btn btn-secondary">Retour</a> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Candidates</title> +<section> + <header class="mb-4"> + <h2 class="fw-bold border-bottom pb-2">Candidate Profile</h2> + </header> + + <!-- Alert Messages --> + <div th:if="${successMessage}" class="alert alert-success alert-dismissible fade show" role="alert"> + <i class="bi bi-check-circle-fill me-2"></i> + <span th:text="${successMessage}"></span> + <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> + </div> + + <div th:if="${errorMessage}" class="alert alert-danger alert-dismissible fade show" role="alert"> + <i class="bi bi-exclamation-triangle-fill me-2"></i> + <span th:text="${errorMessage}"></span> + <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> </div> -</body> -</html> + + <div th:if="${candidate != null}" class="card shadow-sm mb-4"> + <div class="card-body"> + <div class="row g-3"> + <!-- Left Column --> + <div class="col-md-6"> + <h4 class="favorite_color fw-bold mb-3"> + <i class="bi bi-person-badge"></i> Personal Information + </h4> + + <div class="mb-3"> + <label class="form-label fw-semibold">First Name</label> + <div class="form-control-plaintext bg-light p-2 rounded" th:text="${candidate.firstname}"></div> + </div> + + <div class="mb-3"> + <label class="form-label fw-semibold">Last Name</label> + <div class="form-control-plaintext bg-light p-2 rounded" th:text="${candidate.lastname}"></div> + </div> + </div> + + <!-- Right Column --> + <div class="col-md-6"> + <div class="mb-3"> + <label class="form-label fw-semibold">Email</label> + <div class="form-control-plaintext bg-light p-2 rounded" th:text="${candidate.mail}"></div> + </div> + + <div class="mb-3"> + <label class="form-label fw-semibold">City</label> + <div class="form-control-plaintext bg-light p-2 rounded" th:text="${candidate.city}"></div> + </div> + </div> + </div> + + <!-- Job Applications Section --> + <div class="mt-4"> + <h4 class="favorite_color fw-bold mb-3"> + <i class="bi bi-file-earmark-text"></i> Job Applications + </h4> + + <div th:if="${#lists.isEmpty(candidateApplications)}" class="alert alert-info"> + <i class="bi bi-info-circle"></i> No job applications found + </div> + + <div th:unless="${#lists.isEmpty(candidateApplications)}" class="table-responsive"> + <table class="table table-hover align-middle"> + <thead class="table-light"> + <tr> + <th>Application ID</th> + <th>CV Reference</th> + <th>Qualification</th> + <th>Sectors</th> + <th>Applied Date</th> + <th class="text-end">Actions</th> + </tr> + </thead> + <tbody> + <tr th:each="app : ${candidateApplications}"> <!-- Changed from application to app --> + <td th:text="${app.id}"></td> + <td th:text="${app.cv}"></td> + <td th:text="${app.qualificationlevel.label}"></td> + <td> + <div th:each="sector : ${app.sectors}" class="badge favorite_back me-1 mb-1" + th:text="${sector.label}"></div> + </td> + <td th:text="${#temporals.format(app.appdate, 'yyyy-MM-dd')}"></td> + <td class="text-end"> + <span th:if="${session != null and session.userType == 'Candidate' and session.loggedInUser.id == candidate.id}" > + <a th:href="@{'/applications/details/' + ${app.id}}" + class="btn btn-sm btn-outline-secondary me-1"> + <i class="bi bi-eye"></i> View + </a> + <a th:href="@{'/applications/edit/' + ${app.id}}" + class="btn btn-sm btn-outline-warning"> + <i class="bi bi-pencil"></i> Edit + </a> + <a th:href="@{'/applications/delete/' + ${app.id}}" + class="btn btn-sm btn-outline-danger" + onclick="return confirm('Are you sure you want to delete this application?');"> + <i class="bi bi-trash"></i> Delete + </a> + </span> + </td> + </tr> + </tbody> + </table> + </div> + </div> + </div> + </div> + +</section> +</html> \ No newline at end of file diff --git a/target/classes/templates/candidate/editCandidate.html b/target/classes/templates/candidate/editCandidate.html index 8bc1e65..4bb63f8 100644 --- a/target/classes/templates/candidate/editCandidate.html +++ b/target/classes/templates/candidate/editCandidate.html @@ -1,46 +1,176 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Candidates</title> +<section> <head> <meta charset="UTF-8"> - <title>Modifier un Candidat</title> + <title>Edit Candidate</title> <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> + <link rel="stylesheet" th:href="@{/css/bootstrap-icons.css}"> + <style> + .profile-card { + max-width: 700px; + margin: 0 auto; + border-radius: 10px; + overflow: hidden; + } + .profile-header { + background: #00B8DEFF; + color: white; + } + .form-control:read-only { + background-color: #f8f9fa; + border-color: #dee2e6; + } + .password-container { + position: relative; + } + .password-toggle { + position: absolute; + right: 10px; + top: 50%; + transform: translateY(-50%); + cursor: pointer; + z-index: 5; + background: none; + border: none; + color: #6c757d; + } + .btn-primary { + background-color: rgb(0, 184, 222); + border-color: rgb(0, 184, 222); + } + </style> </head> -<body> - <div class="container"> - <h2 class="mt-5">Modifier un Candidat</h2> - - <form th:action="@{/candidates/edit}" method="post"> - <input type="hidden" name="id" th:value="${candidate.id}" /> - - <div class="form-group"> - <label>Email :</label> - <!-- <input type="email" name="mail" class="form-control" th:value="${candidate.mail}" required> --> - <input type="email" class="form-control" name="mail" required th:value="${candidate.mail}" readonly /> +<body class="bg-light"> +<div class="container py-4"> + <div class="card profile-card shadow-lg"> + <div class="card-header profile-header py-3"> + <div class="d-flex justify-content-between align-items-center"> + <h2 class="h4 mb-0"> + <i class="bi bi-person-gear me-2"></i> Edit Candidate Profile + </h2> </div> + </div> - <div class="form-group"> - <label>Mot de passe :</label> - <input type="password" name="password" class="form-control" required> + <div class="card-body p-4"> + <!-- Success Message --> + <div th:if="${successMessage != null}" class="alert alert-success alert-dismissible fade show"> + <i class="bi bi-check-circle-fill me-2"></i> + <span th:text="${successMessage}"></span> + <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> </div> - <div class="form-group"> - <label>Nom :</label> - <input type="text" name="lastname" class="form-control" th:value="${candidate.lastname}" required> + <!-- Error Messages (simplified to avoid Thymeleaf errors) --> + <div th:if="${errorMessage != null}" class="alert alert-danger alert-dismissible fade show"> + <i class="bi bi-exclamation-triangle-fill me-2"></i> + <span th:text="${errorMessage}"></span> + <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> </div> - <div class="form-group"> - <label>Prénom :</label> - <input type="text" name="firstname" class="form-control" th:value="${candidate.firstname}" required> - </div> + <form th:action="@{/candidates/edit}" method="post" class="needs-validation" novalidate> + <input type="hidden" name="id" th:value="${candidate.id}" /> - <div class="form-group"> - <label>Ville :</label> - <input type="text" name="city" class="form-control" th:value="${candidate.city}" required> - </div> + <div class="row g-4"> + <!-- Left Column --> + <div class="col-md-6"> + <div class="mb-3"> + <label class="form-label fw-semibold">Email</label> + <input type="email" class="form-control" name="mail" + th:value="${candidate.mail}" readonly> + <small class="text-muted">Email cannot be changed</small> + </div> + + <div class="mb-3"> + <label class="form-label fw-semibold">Password</label> + <div class="password-container"> + <input type="password" id="passwordField" name="password" + class="form-control" + placeholder="Leave blank to keep current password"> + <button type="button" class="password-toggle" onclick="togglePassword()"> + <i class="bi bi-eye"></i> + </button> + </div> + <small class="text-muted">Minimum 8 characters</small> + </div> + </div> + + <!-- Right Column --> + <div class="col-md-6"> + <div class="mb-3"> + <label class="form-label fw-semibold">First Name</label> + <input type="text" name="firstname" class="form-control" + th:value="${candidate.firstname}" required> + </div> + + <div class="mb-3"> + <label class="form-label fw-semibold">Last Name</label> + <input type="text" name="lastname" class="form-control" + th:value="${candidate.lastname}" required> + </div> - <button type="submit" class="btn btn-success mt-3">Enregistrer</button> - <a th:href="@{/candidates/list}" class="btn btn-secondary mt-3">Annuler</a> - </form> + <div class="mb-3"> + <label class="form-label fw-semibold">City</label> + <input type="text" name="city" class="form-control" + th:value="${candidate.city}" required> + </div> + </div> + </div> + + <div class="d-flex justify-content-between mt-4"> + <div> + <a th:href="@{/candidates/list}" class="btn btn-outline-secondary"> + <i class="bi bi-arrow-left"></i> Back to list + </a> + </div> + + <div > + <button type="submit" class="btn btn-primary px-4"> + <i class="bi bi-save me-2"></i> Save Changes + </button> + </div> + </div> + </form> + </div> </div> +</div> + +<script th:src="@{/js/bootstrap.bundle.min.js}"></script> +<script> + // Password toggle function + function togglePassword() { + const passwordField = document.getElementById('passwordField'); + const toggleIcon = document.querySelector('.password-toggle i'); + + if (passwordField.type === 'password') { + passwordField.type = 'text'; + toggleIcon.classList.replace('bi-eye', 'bi-eye-slash'); + } else { + passwordField.type = 'password'; + toggleIcon.classList.replace('bi-eye-slash', 'bi-eye'); + } + } + + // Form validation + document.addEventListener('DOMContentLoaded', function() { + const form = document.querySelector('.needs-validation'); + + form.addEventListener('submit', function(event) { + if (!form.checkValidity()) { + event.preventDefault(); + event.stopPropagation(); + } + form.classList.add('was-validated'); + }, false); + + // Add input validation on blur + form.querySelectorAll('input[required]').forEach(input => { + input.addEventListener('blur', () => { + input.classList.toggle('is-invalid', !input.checkValidity()); + }); + }); + }); +</script> </body> -</html> +</section> +</html> \ No newline at end of file diff --git a/target/classes/templates/candidate/signupCandidate.html b/target/classes/templates/candidate/signupCandidate.html index f8d74e5..dd6a100 100644 --- a/target/classes/templates/candidate/signupCandidate.html +++ b/target/classes/templates/candidate/signupCandidate.html @@ -1,17 +1,26 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> -<head> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Candidates</title> +<section> + <head> <meta charset="UTF-8"> <title>Candidate Sign Up</title> <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> + <style> + .btn-primary { + background-color: rgb(0, 184, 222); + border-color: rgb(0, 184, 222); + } + </style> </head> <body> <div class="container"> <h2 class="mt-5">Sign Up as Candidate</h2> - <!-- Affichage des erreurs --> - <div th:if="${error}" class="alert alert-danger" th:text="${error}"></div> - + <!-- Affichage des erreurs de connexion --> + <div th:if="${error}" class="alert alert-danger mt-3" role="alert"> + <p class="mb-0" th:text="${error}"></p> + </div> <!-- Formulaire d'inscription --> <form th:action="@{/candidates/signup}" method="post"> <div class="mb-3"> @@ -35,13 +44,15 @@ <input type="text" class="form-control" id="city" name="city" th:value="${city}" required> </div> - <button type="submit" class="btn btn-primary">Sign Up</button> - </form> + <button type="submit" class="btn btn-primary ">Sign Up</button> + <a th:href="@{/login}" class="btn btn-light text-black " >Annuler</a> + </form> <!-- Lien de retour à la page de connexion --> <p class="mt-3"> Already have an account? <a th:href="@{/login}">Sign in here</a> </p> </div> </body> +</section> </html> diff --git a/target/classes/templates/company/companyBase.html b/target/classes/templates/company/companyBase.html deleted file mode 100644 index eed3128..0000000 --- a/target/classes/templates/company/companyBase.html +++ /dev/null @@ -1,13 +0,0 @@ -<!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org" th:fragment="article(subcontent)" - th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> -<title>Companies</title> -<section> - <header> - <h1>Companies Management</h1> - </header> - <th:block th:insert="${subcontent}"> - </th:block> -</section> - -</html> \ No newline at end of file diff --git a/target/classes/templates/company/companyEdit.html b/target/classes/templates/company/companyEdit.html index 82ea247..dea5445 100644 --- a/target/classes/templates/company/companyEdit.html +++ b/target/classes/templates/company/companyEdit.html @@ -1,50 +1,143 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/company/companyBase :: article(~{::article})}"> -<article> - <header> - <h2>Modifier l'entreprise</h2> - </header> - - <!-- Message d'erreur ou de succès --> - <div th:if="${successMessage}" class="alert alert-success"> - <strong>Succès :</strong> <span th:text="${successMessage}"></span> - </div> - - <div th:if="${errorMessage}" class="alert alert-danger"> - <strong>Erreur :</strong> <span th:text="${errorMessage}"></span> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Companies</title> +<section> + <head> + <meta charset="UTF-8"> + <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> + <link rel="stylesheet" th:href="@{/css/bootstrap-icons.css}"> + <style> + .profile-card { + max-width: 800px; + margin: 0 auto; + border-radius: 10px; + overflow: hidden; + } + .profile-header { + background: #00B8DEFF; + color: white; + } + .form-control:read-only { + background-color: #f8f9fa; + border-color: #dee2e6; + } + .btn-primary { + background-color: #00B8DEFF; + border-color: #00B8DEFF; + } + .btn-outline-secondary { + border-color: #6c757d; + color: #6c757d; + } + .form-label { + font-weight: 500; + color: #495057; + } + .text-muted { + font-size: 0.85rem; + } + </style> + </head> + <body class="bg-light"> + <div class="container py-4"> + <div class="card profile-card shadow-lg"> + <div class="card-header profile-header py-3"> + <div class="d-flex justify-content-between align-items-center"> + <h2 class="h4 mb-0"> + <i class="bi bi-building-gear me-2"></i> Edit Company Profile + </h2> + </div> + </div> + + <div class="card-body p-4"> + <!-- Alert Messages --> + <div th:if="${successMessage}" class="alert alert-success alert-dismissible fade show"> + <i class="bi bi-check-circle-fill me-2"></i> + <span th:text="${successMessage}"></span> + <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> + </div> + + <div th:if="${error}" class="alert alert-danger alert-dismissible fade show"> + <i class="bi bi-exclamation-triangle-fill me-2"></i> + <span th:text="${error}"></span> + <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> + </div> + + <form th:action="@{/companies/update}" method="post" class="needs-validation" novalidate> + <input type="hidden" name="id" th:value="${company.id}" /> + + <div class="row g-4"> + <!-- Left Column --> + <div class="col-md-6"> + <div class="mb-3"> + <label class="form-label">Email</label> + <input type="email" class="form-control" name="mail" + th:value="${company.mail}" readonly> + <small class="text-muted">Email cannot be changed</small> + </div> + + <div class="mb-3"> + <label class="form-label">Company Name</label> + <input type="text" class="form-control" name="denomination" + th:value="${company.denomination}" required> + </div> + </div> + + <!-- Right Column --> + <div class="col-md-6"> + <div class="mb-3"> + <label class="form-label">Description</label> + <textarea class="form-control" name="description" + rows="3" required th:text="${company.description}"></textarea> + </div> + + <div class="mb-3"> + <label class="form-label">City</label> + <input type="text" class="form-control" name="city" + th:value="${company.city}" required> + </div> + </div> + </div> + + <div class="d-flex justify-content-between mt-4"> + <div> + <a th:href="@{/companies}" class="btn btn-outline-secondary"> + <i class="bi bi-arrow-left"></i> Back to list + </a> + </div> + <div> + <button type="submit" class="btn btn-primary px-4"> + <i class="bi bi-save me-2"></i> Save Changes + </button> + </div> + </div> + </form> + </div> + </div> </div> - - <form th:action="@{/companies/update}" method="post"> - <!-- ID (caché) --> - <input type="hidden" name="id" th:value="${company.id}" /> - - <!-- Email (readonly) --> - <fieldset class="mb-3"> - <label for="emailid" class="form-label">Email</label>: - <input type="email" id="emailid" class="form-control" name="mail" th:value="${company.mail}" readonly /> - </fieldset> - - <!-- Dénomination --> - <fieldset class="mb-3"> - <label for="nameid" class="form-label">Nom</label>: - <input type="text" id="nameid" class="form-control" name="denomination" th:value="${company.denomination}" required /> - </fieldset> - - <!-- Description --> - <fieldset class="mb-3"> - <label for="descid" class="form-label">Description</label>: - <input type="text" id="descid" class="form-control" name="description" th:value="${company.description}" required /> - </fieldset> - - <!-- Ville --> - <fieldset class="mb-3"> - <label for="cityid" class="form-label">Ville</label>: - <input type="text" id="cityid" class="form-control" name="city" th:value="${company.city}" required /> - </fieldset> - - <!-- Boutons --> - <button type="submit" class="btn btn-success">💾 Enregistrer</button> - <a th:href="@{/companies/view/{id}(id=${company.id})}" class="btn btn-danger">❌ Annuler</a> - </form> -</article> -</html> + + <script th:src="@{/js/bootstrap.bundle.min.js}"></script> + <script> + // Form validation + document.addEventListener('DOMContentLoaded', function() { + const form = document.querySelector('.needs-validation'); + + form.addEventListener('submit', function(event) { + if (!form.checkValidity()) { + event.preventDefault(); + event.stopPropagation(); + } + form.classList.add('was-validated'); + }, false); + + // Add input validation on blur + form.querySelectorAll('input[required], textarea[required]').forEach(input => { + input.addEventListener('blur', () => { + input.classList.toggle('is-invalid', !input.checkValidity()); + }); + }); + }); + </script> + </body> +</section> +</html> \ No newline at end of file diff --git a/target/classes/templates/company/companyForm.html b/target/classes/templates/company/companyForm.html index e594bca..ca2b0a6 100644 --- a/target/classes/templates/company/companyForm.html +++ b/target/classes/templates/company/companyForm.html @@ -1,6 +1,7 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/company/companyBase :: article(~{::article})}"> -<article> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Companies</title> +<section> <header> <h2>Company entry form</h2> </header> @@ -10,23 +11,23 @@ <strong>Succès :</strong> <span th:text="${successMessage}"></span> </div> - <!-- ❌ Message d'erreur --> - <div th:if="${errorMessage}" class="alert alert-danger"> - <strong>Erreur :</strong> <span th:text="${errorMessage}"></span> + <!-- Affichage des erreurs de connexion --> + <div th:if="${error}" class="alert alert-danger mt-3" role="alert"> + <p class="mb-0" th:text="${error}"></p> </div> <form th:action="@{/companies/create}" method="post"> <!-- Email --> <fieldset class="mb-3"> <label for="emailid" class="form-label">Email Address</label>: - <input type="email" id="emailid" class="form-control" name="mail" + <input type="email" id="emailid" class="form-control" name="mail" th:value="${company.mail}" required /> </fieldset> <!-- Mot de passe --> <fieldset class="mb-3"> <label for="passwordid" class="form-label">Password</label>: - <input type="password" id="passwordid" class="form-control" name="password" + <input type="password" id="passwordid" class="form-control" name="password" minlength="4" required /> </fieldset> @@ -40,20 +41,20 @@ <!-- 📌 CHAMP DESCRIPTION (Ajouté ici) --> <fieldset class="mb-3"> <label for="descid" class="form-label">Description</label>: - <input type="text" id="descid" class="form-control" name="description" + <input type="text" id="descid" class="form-control" name="description" th:value="${company.description}" required /> </fieldset> <!-- Ville --> <fieldset class="mb-3"> <label for="cityid" class="form-label">City</label>: - <input type="text" id="cityid" class="form-control" name="city" + <input type="text" id="cityid" class="form-control" name="city" th:value="${company.city}" required /> </fieldset> <!-- Boutons --> - <button type="submit" class="btn btn-primary">Enregistrer</button> - <a th:href="@{/companies}" class="btn btn-danger">Annuler</a> + <button type="submit" class="btn favorite_back text-white">Save</button> + <a th:href="@{/login}" class="btn btn-light text-black " >Cancel</a> </form> -</article> +</section> </html> \ No newline at end of file diff --git a/target/classes/templates/company/companyList.html b/target/classes/templates/company/companyList.html index 76c1e01..5e793d4 100644 --- a/target/classes/templates/company/companyList.html +++ b/target/classes/templates/company/companyList.html @@ -1,49 +1,108 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> -<head> - <title>Liste des Entreprises</title> - <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> -</head> -<body> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Companies</title> +<section> -<div class="container mt-4"> - <h2 class="text-center mb-4">Liste des Entreprises</h2> + <head> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"> + <style> + .company-card { + transition: all 0.3s ease; + border-left: 4px solid transparent; + } + .company-card:hover { + transform: translateY(-2px); + border-left-color: #00b8de; + background-color: #f8f9fa; + } + .description-cell { + max-width: 300px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + .table-container { + border-radius: 8px; + overflow: hidden; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); + } + .content-footer { + margin-top: 5rem; + padding-top: 1rem; + border-top: 1px solid #dee2e6; + } + </style> + </head> + <body class="bg-light"> - <table class="table table-striped table-bordered table-hover"> - <thead class="table-dark"> - <tr> - <th>ID</th> - <th>Dénomination</th> - <th>Ville</th> - <th>Description</th> - <th>Nombre d'Offres</th> - <th>Actions</th> - </tr> - </thead> - <tbody> - <tr th:each="company : ${companies}"> - <td th:text="${company.id}"></td> - <td th:text="${company.denomination}"></td> - <td th:text="${company.city}"></td> - <td th:text="${company.description}"></td> - <td th:text="${company.getJobOfferCount()}"></td> - <td> - <a th:href="@{'/companies/view/' + ${company.id}}" class="btn btn-primary btn-sm">Voir</a> - </td> - </tr> - </tbody> - </table> + <div class="container py-4"> + <!-- Header Section --> + <div class="d-flex justify-content-between align-items-center mb-4"> + <h2 class="fw-bold text-black mb-0"> + <i class=" color-title bi bi-buildings me-2"></i> Lists of Companies + </h2> + </div> - <a th:href="@{/}" class="btn btn-secondary mt-3">🏠 Retour à l'accueil</a> + <!-- Companies Table --> + <div class="card shadow-sm table-container"> + <div class="card-body p-0"> + <div class="table-responsive"> + <table class="table table-hover align-middle mb-0" th:if="${not #lists.isEmpty(companies)}"> + <thead class="table-light"> + <tr> + <th class="ps-4">Company Name</th> + <th>Location</th> + <th>Description</th> + <th>Job Offers</th> + <th class="text-end pe-4">Actions</th> + </tr> + </thead> + <tbody> + <tr th:each="company : ${companies}" class="company-card"> + <td class="ps-4 fw-semibold" th:text="${company.denomination}"></td> + <td> + <i class="bi bi-geo-alt text-secondary me-1"></i> + <span th:text="${company.city}"></span> + </td> + <td class="description-cell" th:text="${company.description}"></td> + <td> + <span class="badge favorite_back rounded-pill" + th:text="${company.getJobOfferCount()}"></span> + </td> + <td class="text-end pe-4"> + <div class="d-flex gap-2 justify-content-end"> + <a th:href="@{'/companies/view/' + ${company.id}}" + class="btn btn favorite_outline" > + <i class="bi bi-eye"></i> View + </a> + <th:block th:if="${session != null and session.userType == 'Company' and session.loggedInUser.id == company.id}"> + <a th:href="@{'/companies/edit/' + ${company.id}}" + class="btn btn-sm btn-outline-warning"> + <i class="bi bi-pencil"></i> Edit + </a> + <a th:href="@{'/companies/delete/' + ${company.id}}" + class="btn btn-sm btn-outline-danger" + onclick="return confirm('Are you sure you want to delete this company offer?');"> + <i class="bi bi-trash"></i> Delete + </a> + </th:block> + </div> + </td> + </tr> + </tbody> + </table> + <!-- Empty State --> + <div th:if="${#lists.isEmpty(companies)}" class="text-center py-5"> + <i class="bi bi-buildings empty-state-icon text-muted"></i> + <h4 class="mt-3 text-muted">No companies registered</h4> + </div> + </div> + </div> + </div> + </div> - - <!-- Vérifie si l'utilisateur est authentifié ET est une entreprise --> - <th:block th:if="${#httpServletRequest != null and #httpServletRequest.getSession(false) != null and #httpServletRequest.getSession().getAttribute('usertype') == 'company'}"> - <a th:href="@{/companies/create}" class="btn btn-success">Sign up as a company</a> - </th:block> - -</div> - -<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> -</body> -</html> + <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> + </body> +</section> +</html> \ No newline at end of file diff --git a/target/classes/templates/company/companyView.html b/target/classes/templates/company/companyView.html index 7d5f091..39baaca 100644 --- a/target/classes/templates/company/companyView.html +++ b/target/classes/templates/company/companyView.html @@ -1,123 +1,133 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org" - th:replace="~{/company/companyBase :: article(~{::article})}"> -<article> - <header> - <h2>Company details</h2> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Companies</title> +<section> + <header class="mb-4"> + <h2 class="fw-bold border-bottom pb-2">Company Details</h2> </header> - - <div th:if="${successMessage}" class="alert alert-success"> - <strong>Succès :</strong> <span th:text="${successMessage}"></span> + <!-- Alert Messages --> + <div th:if="${successMessage}" class="alert alert-success alert-dismissible fade show" role="alert"> + <i class="bi bi-check-circle-fill me-2"></i> + <span th:text="${successMessage}"></span> + <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> </div> - - <div th:if="${errorMessage}" class="alert alert-danger"> - <strong>Erreur :</strong> <span th:text="${errorMessage}"></span> + + <div th:if="${errorMessage}" class="alert alert-danger alert-dismissible fade show" role="alert"> + <i class="bi bi-exclamation-triangle-fill me-2"></i> + <span th:text="${errorMessage}"></span> + <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> </div> - <div th:if="${company != null}"> - <form> - <fieldset class="mb-3"> - <label for="nameid" class="form-label">Name</label>: - <input type="text" id="nameid" class="form-control" name="denomination" - th:value="${company.denomination}" readonly /> - </fieldset> - <fieldset class="mb-3"> - <label for="emailid" class="form-label">Email</label>: - <input type="text" id="emailid" class="form-control" name="email" - th:value="${company.email}" readonly /> - </fieldset> - - <fieldset class="mb-3"> - <label for="descid" class="form-label">Description</label>: - <input type="text" id="descid" class="form-control" name="description" - th:value="${company.description}" readonly /> - </fieldset> - <fieldset class="mb-3"> - <label for="cityid" class="form-label">City</label>: - <input type="text" id="cityid" class="form-control" name="city" - th:value="${company.city}" readonly /> - </fieldset> - - - <fieldset class="mb-3"> - <label class="form-label">Nombre d'offres d'emploi publiées :</label> - <input type="text" class="form-control" th:value="${jobOfferCount}" readonly /> - </fieldset> - - <fieldset class="mb-3"> - <h3>📋 Offres d'emploi publiées</h3> - <table class="table table-striped table-bordered"> - <thead class="table-dark"> + <div th:if="${company != null}" class="card shadow-sm mb-4"> + <div class="card-body"> + <div class="row g-3"> + <!-- Left Column --> + <div class="col-md-6"> + <h4 class="favorite_color fw-bold mb-3"> + <i class="bi bi-building"></i> Company Information + </h4> + + <div class="mb-3"> + <label class="form-label fw-semibold">Company Name</label> + <div class="form-control-plaintext bg-light p-2 rounded" th:text="${company.denomination}"></div> + </div> + + <div class="mb-3"> + <label class="form-label fw-semibold">Email</label> + <div class="form-control-plaintext bg-light p-2 rounded" th:text="${company.email}"></div> + </div> + + <div class="mb-3"> + <label class="form-label fw-semibold">City</label> + <div class="form-control-plaintext bg-light p-2 rounded" th:text="${company.city}"></div> + </div> + </div> + + <!-- Right Column (Aligned with left column) --> + <div class="col-md-6"> + <div class="mb-3" style="min-height: 96px"> + <label class="form-label fw-semibold">Description</label> + <div class="form-control-plaintext bg-light p-2 rounded" style="min-height: 96px" th:text="${company.description}"></div> + </div> + + <div class="mb-3"> + <label class="form-label fw-semibold">Job Offers</label> + <div class="form-control-plaintext bg-light p-2 rounded fw-bold" + th:text="${jobOfferCount} + (${jobOfferCount} == 1 ? ' active job' : ' active jobs')"></div> + </div> + </div> + </div> + + <!-- Job Offers Section --> + <div class="mt-4"> + <h4 class="favorite_color fw-bold mb-3"> + <i class="bi bi-briefcase"></i> Current Job Offers + </h4> + + <div th:if="${#lists.isEmpty(jobOffers)}" class="alert alert-info"> + <i class="bi bi-info-circle"></i> No active job openings currently + </div> + + <div th:unless="${#lists.isEmpty(jobOffers)}" class="table-responsive"> + <table class="table table-hover align-middle"> + <thead class="table-light"> <tr> <th>ID</th> - <th>Titre</th> + <th>Position</th> <th>Description</th> - <th>Action</th> + <th>Sectors</th> + <th>Qualification</th> + <th>Posted Date</th> + <th class="text-end">Actions</th> </tr> - </thead> - <tbody> + </thead> + <tbody> <tr th:each="offer : ${jobOffers}"> <td th:text="${offer.id}"></td> <td th:text="${offer.title}"></td> - <td th:text="${offer.taskDescription}"></td> - <td> - <a th:href="@{/jobOffers/view/{id}(id=${offer.id})}" class="btn btn-info">Voir l'offre</a> + <div class="text-truncate" style="max-width: 300px" th:text="${offer.taskDescription}"></div> + </td> + <td> + <div class="d-flex flex-wrap"> + <span th:each="sector : ${offer.sectors}" + class="badge favorite_back sector-badge" + th:text="${sector.label}"></span> + </div> + </td> + <td th:text="${offer.qualificationLevel.label}"></td> + <td th:text="${offer.publicationDate != null} ? ${#temporals.format(offer.publicationDate, 'yyyy-MM-dd')} : 'N/A'"></td> + <td class="text-end" > + <a th:href="@{/jobs/view/{id}(id=${offer.id})}" th:if="${session != null and session.userType == 'Company' and session.loggedInUser.id == company.id}" + class="btn btn-sm text-black"> + <i class="bi bi-eye"></i> View + </a> + <th:block th:if="${session != null and session.userType == 'Company' and session.loggedInUser.id == company.id}"> + <a th:href="@{'/jobs/edit/' + ${offer.id}}" + class="btn btn-sm btn-outline-warning"> + <i class="bi bi-pencil"></i> Edit + </a> + <a th:href="@{'/jobs/delete/' + ${offer.id}}" + class="btn btn-sm btn-outline-danger" + onclick="return confirm('Are you sure you want to delete this job offer?');"> + <i class="bi bi-trash"></i> Delete + </a> + </th:block> </td> </tr> - <tr th:if="${#lists.isEmpty(jobOffers)}"> - <td colspan="4" class="text-center text-danger">⚠️ Aucune offre d'emploi trouvée.</td> - </tr> - </tbody> - </table> - </fieldset> - - -<!-- <fieldset class="mb-3"> - <h3>📋 Offres d'emploi publiées</h3> - <table class="table table-striped table-bordered"> - <thead class="table-dark"> - <tr> - <th>ID</th> - <th>Titre</th> - <th>Description</th> - </tr> - </thead> - <tbody> - <tr th:each="offer : ${jobOffers}"> - <td th:text="${offer.id}"></td> - <td th:text="${offer.title}"></td> - <td th:text="${offer.taskDescription}"></td> - </tr> - <tr th:if="${#lists.isEmpty(jobOffers)}"> - <td colspan="3" class="text-center text-danger">⚠️ Aucune offre d'emploi trouvée.</td> - </tr> - </tbody> - </table> -</fieldset> --> -</form> - + </tbody> + </table> + </div> + </div> + </div> </div> - <div th:if="${company == null}"> - <p class="text-danger">❌ Erreur : Aucune entreprise trouvée.</p> - </div> - <footer> - <a th:href="@{/companies/{id}/edit(id=${company.id})}" class="btn btn-primary" title="Modifier"> - ✏️ Modifier - </a> - <!-- <a th:href="@{/companies/{id}/jobOffers(id=${company.id})}" class="btn btn-info"> - 📄 Voir les offres publiées - </a> --> - - - <a th:href="@{/companies/delete/{id}(id=${company.id})}" class="btn btn-danger" - onclick="return confirm('⚠️ Êtes-vous sûr de vouloir supprimer cette entreprise ?');"> - 🗑 Supprimer - </a> - - </footer> -</article> -</html> + + + <div class="mt-3"> + + </div> +</section> +</html> \ No newline at end of file diff --git a/target/classes/templates/error/accessDenied.html b/target/classes/templates/error/accessDenied.html index d4ed95d..6ddc6e7 100644 --- a/target/classes/templates/error/accessDenied.html +++ b/target/classes/templates/error/accessDenied.html @@ -9,7 +9,7 @@ <div class="container mt-5 text-center"> <h2 class="text-danger">⛔ Accès interdit</h2> <p>Vous n'avez pas les permissions nécessaires pour voir cette page.</p> - <a href="/" class="btn btn-primary">Retour à l'accueil</a> + <a href="/" class="btn favorite_back">Retour à l'accueil</a> </div> </body> diff --git a/target/classes/templates/index.html b/target/classes/templates/index.html index d53f468..347b164 100644 --- a/target/classes/templates/index.html +++ b/target/classes/templates/index.html @@ -59,6 +59,31 @@ </a> </article> --> + + <!-- Nouvelle section profil améliorée --> + <div th:if="${session.loggedInUser != null}" > + + <div class="d-flex justify-content-between align-items-center text-black"> + <div> + <h2> + <i th:classappend="${session.userType == 'Company'} ? 'bi-building' : 'bi-person'" + class="bi me-2"></i> + WELCOME, + <span th:if="${session.userType == 'Company'}"> + [[${session.loggedInUser.denomination}]] + </span> + <span th:if="${session.userType == 'Candidate'}"> + [[${session.loggedInUser.firstname}]] [[${session.loggedInUser.lastname}]] + </span> + </h2> + </div> + <div> + <a th:if="${session.hasMessages}" th:href="@{/messages}" class="btn btn-outline-light me-2"> + <i class="bi bi-envelope-at"></i> Messages + </a> + </div> + </div> + </div> <div class="row row-cols-1 row-cols-lg-3 align-items-stretch g-4 py-5"> <article class="col"> <a th:href="@{/companies}" class="nav-link card card-cover h-100 overflow-hidden text-white bg-dark rounded-5 shadow-lg" @@ -70,7 +95,7 @@ </article> <article class="col"> - <a th:href="@{/candidates}" class="nav-link card card-cover h-100 overflow-hidden text-white bg-dark rounded-5 shadow-lg" + <a th:href="@{/candidates/list}" class="nav-link card card-cover h-100 overflow-hidden text-white bg-dark rounded-5 shadow-lg" style="background-image: url('/img/candidates.jpg'); background-size: cover;"> <div class="d-flex flex-column h-100 p-5 pb-3 text-white text-shadow-1"> <h2 class="pt-5 mt-5 mb-4 display-6 lh-1 fw-bold">Candidates</h2> @@ -79,7 +104,7 @@ </article> <!-- ✅ Déplacement de "Postuler" dans la même ligne --> - <article class="col"> + <article class="col" th:if="${session!=null and session.userType=='Candidate'} "> <a th:href="@{/applications/apply}" class="nav-link card card-cover h-100 overflow-hidden text-white bg-dark rounded-5 shadow-lg" style="background-image: url('/img/postuler.jpeg'); background-size: cover;"> <div class="d-flex flex-column h-100 p-5 pb-3 text-white text-shadow-1"> diff --git a/target/classes/templates/jobOffer/companyJobOfferView.html b/target/classes/templates/jobOffer/companyJobOfferView.html index 37cb1c2..1ce5550 100644 --- a/target/classes/templates/jobOffer/companyJobOfferView.html +++ b/target/classes/templates/jobOffer/companyJobOfferView.html @@ -1,19 +1,22 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Create Job Offer</title> +<section> <head> <title>Détails de l'Offre d'Emploi</title> <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"> </head> <body> <div class="container"> - <h2>Offre d'Emploi : <span th:text="${jobOffer.title}"></span></h2> + <h2>Offre d'Emploi : <span th:text="${offer.title}"></span></h2> - <p><strong>ID:</strong> <span th:text="${jobOffer.id}"></span></p> - <p><strong>Titre:</strong> <span th:text="${jobOffer.title}"></span></p> - <p><strong>Description:</strong> <span th:text="${jobOffer.taskDescription}"></span></p> - <p><strong>Date de Publication:</strong> <span th:text="${jobOffer.publicationDate}"></span></p> + <p><strong>ID:</strong> <span th:text="${offer.id}"></span></p> + <p><strong>Titre:</strong> <span th:text="${offer.title}"></span></p> + <p><strong>Description:</strong> <span th:text="${offer.taskDescription}"></span></p> + <p><strong>Date de Publication:</strong> <span th:text="${offer.publicationDate}"></span></p> <a th:href="@{/companies}" class="btn btn-secondary">Retour aux entreprises</a> </div> </body> +</section> </html> diff --git a/target/classes/templates/jobOffer/jobOfferEdit.html b/target/classes/templates/jobOffer/jobOfferEdit.html new file mode 100644 index 0000000..0806a8b --- /dev/null +++ b/target/classes/templates/jobOffer/jobOfferEdit.html @@ -0,0 +1,217 @@ +<!DOCTYPE html> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Edit Job Offer</title> +<section> + <head> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"> + <style> + .form-container { + max-width: 800px; + margin: 0 auto; + background-color: #fff; + border-radius: 8px; + padding: 2rem; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + } + .sector-item { + display: inline-block; + margin: 5px; + cursor: pointer; + padding: 5px 10px; + border-radius: 4px; + background: #f8f9fa; + border: 1px solid #dee2e6; + transition: all 0.3s ease; + } + .sector-item.selected { + background: rgb(0, 184, 222); + color: white; + border-color: rgb(0, 184, 222); + } + #selectedSectorsDisplay { + min-height: 40px; + border: 1px solid #ced4da; + border-radius: 4px; + padding: 8px; + margin-top: 5px; + background-color: #f8f9fa; + } + .selected-sector-tag { + display: inline-block; + background: rgb(0, 184, 222); + color: white; + padding: 2px 8px; + border-radius: 4px; + margin-right: 5px; + margin-bottom: 5px; + } + .btn-primary { + background-color: rgb(0, 184, 222); + border-color: rgb(0, 184, 222); + } + </style> + </head> + <body class="bg-light"> + + <div class="container py-4"> + <div class="form-container"> + <h2 class="text-center mb-4"> + <i class="bi bi-briefcase me-2"></i> + Edit Job Offer + </h2> + + <form th:action="@{/jobs/update}" method="post" class="needs-validation" novalidate> + <input type="hidden" name="id" th:value="${job.id}" /> + + <!-- Champs de base --> + <div class="mb-3"> + <label class="form-label">Job Title*</label> + <input type="text" class="form-control" th:value="${job.title}" name="title" required> + </div> + + <div class="mb-3"> + <label class="form-label">Description*</label> + <textarea class="form-control" rows="4" name="taskDescription" required + th:text="${job.taskDescription}"></textarea> + </div> + + <div class="row mb-3"> + <div class="col-md-6"> + <label class="form-label">Publication Date*</label> + <input type="date" class="form-control" name="publicationDate" required + th:value="${job.publicationDate != null} ? ${#temporals.format(job.publicationDate, 'yyyy-MM-dd')} : ${#temporals.format(#temporals.createToday(), 'yyyy-MM-dd')}"/> + </div> + <div class="col-md-6"> + <label class="form-label">Qualification Level*</label> + <select class="form-select" name="qualificationLevel" required> + <option value="">-- Select level --</option> + <option th:each="level : ${qualificationLevels}" + th:value="${level.id}" + th:text="${level.label}" + th:selected="${job.qualificationLevel?.id == level.id}"> + </option> + </select> + </div> + </div> + + <!-- Industry Sectors - Version corrigée --> + <div class="mb-3"> + <label class="form-label">Industry Sectors*</label> + + <!-- Liste complète des secteurs --> + <div class="mb-2"> + <div th:each="sector : ${sectors}" + class="sector-item" + th:data-id="${sector.id}" + th:classappend="${#lists.contains(job.sectors.![id], sector.id)} ? 'selected' : ''" + th:text="${sector.label}" + onclick="toggleSector(this)"></div> + </div> + + <!-- Champ affichant les secteurs sélectionnés --> + <div id="selectedSectorsDisplay" class="mb-2"> + <span th:if="${job.sectors.empty}">No sectors selected</span> + <th:block th:each="sector : ${job.sectors}"> + <div th:data-id="${sector.id}"> + <span th:text="${sector.label}"></span> + </div> + </th:block> + </div> + + <input type="hidden" id="sectorIds" name="sectorIds" + th:value="${#strings.listJoin(job.sectors.![id], ',')}" required> + <div class="invalid-feedback">Please select at least one sector</div> + </div> + + <div class="d-flex justify-content-between mt-4"> + <a th:href="@{/jobs}" class="btn btn-outline-secondary"> + <i class="bi bi-arrow-left me-1"></i> Cancel + </a> + <button type="submit" class="btn btn-primary"> + <i class="bi bi-save me-1"></i> Save Changes + </button> + </div> + </form> + </div> + </div> + + <script> + // Initialize with already selected sectors from the job object + const selectedSectors = new Set( + document.getElementById('sectorIds').value.split(',').filter(Boolean) + ); + + // Mark selected sectors on page load + document.addEventListener('DOMContentLoaded', function() { + updateSelectedDisplay(); + + // Mark initially selected sectors in the list + selectedSectors.forEach(sectorId => { + const element = document.querySelector(`.sector-item[data-id="${sectorId}"]`); + if (element) { + element.classList.add('selected'); + } + }); + }); + + function toggleSector(element) { + const sectorId = element.getAttribute('data-id'); + const sectorLabel = element.textContent; + + if (selectedSectors.has(sectorId)) { + selectedSectors.delete(sectorId); + element.classList.remove('selected'); + } else { + selectedSectors.add(sectorId); + element.classList.add('selected'); + } + + updateSelectedDisplay(); + } + + function updateSelectedDisplay() { + const displayDiv = document.getElementById('selectedSectorsDisplay'); + const hiddenInput = document.getElementById('sectorIds'); + + // Clear the display + displayDiv.innerHTML = ''; + + if (selectedSectors.size === 0) { + displayDiv.innerHTML = '<span>No sectors selected</span>'; + } else { + // Add selected sectors as tags + selectedSectors.forEach(sectorId => { + const sectorElement = document.querySelector(`.sector-item[data-id="${sectorId}"]`); + if (sectorElement) { + const tag = document.createElement('div'); + tag.setAttribute('data-id', sectorId); + tag.innerHTML = `<span>${sectorElement.textContent}</span>`; + displayDiv.appendChild(tag); + } + }); + } + + // Update hidden input with selected sector IDs + hiddenInput.value = Array.from(selectedSectors).join(','); + } + + // Form validation + (function() { + 'use strict'; + const forms = document.querySelectorAll('.needs-validation'); + + Array.from(forms).forEach(form => { + form.addEventListener('submit', event => { + if (!form.checkValidity()) { + event.preventDefault(); + event.stopPropagation(); + } + form.classList.add('was-validated'); + }, false); + }); + })(); + </script> + </body> +</section> +</html> \ No newline at end of file diff --git a/target/classes/templates/jobOffer/jobOfferForm.html b/target/classes/templates/jobOffer/jobOfferForm.html index 7d27094..a6c538b 100644 --- a/target/classes/templates/jobOffer/jobOfferForm.html +++ b/target/classes/templates/jobOffer/jobOfferForm.html @@ -1,70 +1,164 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> -<head> - <title>Créer/Modifier une offre d'emploi</title> - <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> -</head> -<body> - -<div class="container mt-4"> - <h2 class="text-center mb-4">Créer une offre d'emploi</h2> - - <!-- ✅ Affichage des messages d'erreur --> - <div th:if="${errorMessage}" class="alert alert-danger text-center"> - <span th:text="${errorMessage}"></span> - </div> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Create Job Offer</title> +<section> + <head> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"> + <style> + .form-container { + max-width: 800px; + margin: 0 auto; + background-color: #fff; + border-radius: 8px; + padding: 2rem; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + } + .sector-item { + display: inline-block; + margin: 5px; + cursor: pointer; + padding: 5px 10px; + border-radius: 4px; + background: #f8f9fa; + border: 1px solid #dee2e6; + } + .sector-item.selected { + background: rgb(0, 184, 222); + color: white; + border-color: rgb(0, 184, 222); + } + #selectedSectors { + min-height: 40px; + border: 1px solid #ced4da; + border-radius: 4px; + padding: 8px; + margin-top: 5px; + } + .hidden-input { + display: none; + } + .btn-primary { + background-color: rgb(0, 184, 222); + border-color: rgb(0, 184, 222); + } + </style> + </head> + <body class="bg-light"> - <form th:action="@{${jobOffer.id != null} ? '/jobs/update/' + jobOffer.id : '/jobs/save'}" - th:object="${jobOffer}" method="post"> + <div class="container py-4"> + <div class="form-container"> + <h2 class="text-center mb-4"> + <i class="bi bi-briefcase me-2"></i> + Create Job Offer + </h2> - <input type="hidden" th:field="*{id}" /> + <form th:action="@{/jobs/save}" method="post" class="needs-validation" novalidate> - <div class="mb-3"> - <label class="form-label">Titre :</label> - <input type="text" class="form-control" th:field="*{title}" required> - </div> + <!-- Champs de base --> + <div class="mb-3"> + <label class="form-label">Job Title*</label> + <input type="text" class="form-control" name="title" required> + </div> - <div class="mb-3"> - <label class="form-label">Description :</label> - <textarea class="form-control" rows="3" th:field="*{taskDescription}" required></textarea> - </div> + <div class="mb-3"> + <label class="form-label">Description*</label> + <textarea class="form-control" rows="4" name="taskDescription" required></textarea> + </div> - <div class="mb-3"> - <label class="form-label">Date de publication :</label> - <input type="date" class="form-control" th:field="*{publicationDate}" required> - </div> + <div class="row mb-3"> + <div class="col-md-6"> + <label class="form-label">Publication Date*</label> + <input type="date" class="form-control" name="publicationDate" required + th:value="${jobOffer.publicationDate != null} ? ${#temporals.format(jobOffer.publicationDate, 'yyyy-MM-dd')} : ${#temporals.format(#temporals.createToday(), 'yyyy-MM-dd')}"/> + </div> + <div class="col-md-6"> + <label class="form-label">Qualification Level*</label> + <select class="form-select" name="qualificationLevel" required> + <option value="">-- Select level --</option> + <option th:each="level : ${qualificationLevels}" + th:value="${level.id}" + th:text="${level.label}"></option> + </select> + </div> + </div> - <div class="mb-3"> - <label class="form-label">Entreprise :</label> - <select class="form-select" th:field="*{company}" required> - <option value="">-- Sélectionner une entreprise --</option> - <option th:each="company : ${companies}" th:value="${company.id}" th:text="${company.denomination}"></option> - </select> - </div> + <!-- Sélection des secteurs --> + <div class="mb-3"> + <label class="form-label">Industry Sectors*</label> + <div class="mb-2"> + <div th:each="sector : ${sectors}" + class="sector-item" + th:data-id="${sector.id}" + th:text="${sector.label}" + onclick="toggleSector(this)"></div> + </div> + <div id="selectedSectors" class="mb-2">No sectors selected</div> + <input type="hidden" id="sectorIds" name="sectorIds" required> + <div class="invalid-feedback">Please select at least one sector</div> + </div> - <div class="mb-3"> - <label class="form-label">Niveau de qualification :</label> - <select class="form-select" th:field="*{qualificationLevel}"> - <option value="0">-- Sélectionner un niveau de qualification --</option> - <option th:each="level : ${qualificationLevels}" th:value="${level.id}" th:text="${level.label}"></option> - </select> + <div class="d-flex justify-content-between mt-4"> + <a href="/jobs" class="btn btn-outline-secondary"> + <i class="bi bi-arrow-left me-1"></i> Cancel + </a> + <button type="submit" class="btn btn-primary"> + <i class="bi bi-save me-1"></i> Create Offer + </button> + </div> + </form> </div> + </div> - <div class="mb-3"> - <label class="form-label">Secteurs d'activité :</label> - <select class="form-select" multiple th:name="sectorIds"> - <option th:each="sector : ${sectors}" th:value="${sector.id}" th:text="${sector.label}"></option> - </select> - </div> + <script> + const selectedSectors = new Set(); - <div class="text-center"> - <button type="submit" class="btn btn-success">Enregistrer</button> - <a href="/jobs" class="btn btn-secondary">Retour</a> - </div> + function toggleSector(element) { + const sectorId = element.getAttribute('data-id'); + + if (selectedSectors.has(sectorId)) { + selectedSectors.delete(sectorId); + element.classList.remove('selected'); + } else { + selectedSectors.add(sectorId); + element.classList.add('selected'); + } + + updateSelectedDisplay(); + } + + function updateSelectedDisplay() { + const displayDiv = document.getElementById('selectedSectors'); + const hiddenInput = document.getElementById('sectorIds'); + + if (selectedSectors.size === 0) { + displayDiv.textContent = 'No sectors selected'; + hiddenInput.value = ''; + } else { + displayDiv.textContent = Array.from(selectedSectors).map(id => { + return document.querySelector(`.sector-item[data-id="${id}"]`).textContent; + }).join(', '); + + hiddenInput.value = Array.from(selectedSectors).join(','); + } + } - </form> -</div> + // Form validation + (function() { + 'use strict'; + const forms = document.querySelectorAll('.needs-validation'); -<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> -</body> -</html> + Array.from(forms).forEach(form => { + form.addEventListener('submit', event => { + if (!form.checkValidity()) { + event.preventDefault(); + event.stopPropagation(); + } + form.classList.add('was-validated'); + }, false); + }); + })(); + </script> + </body> +</section> +</html> \ No newline at end of file diff --git a/target/classes/templates/jobOffer/jobOfferList.html b/target/classes/templates/jobOffer/jobOfferList.html index ea6688d..228e5a0 100644 --- a/target/classes/templates/jobOffer/jobOfferList.html +++ b/target/classes/templates/jobOffer/jobOfferList.html @@ -1,85 +1,123 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> +<title>Jobs</title> +<section> <head> - <title>Liste des Offres d'Emploi</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"> + <style> + .job-card { + transition: all 0.3s ease; + border-left: 4px solid transparent; + } + .job-card:hover { + transform: translateY(-2px); + border-left-color: #00b8de; + background-color: #f8f9fa; + } + .table-container { + border-radius: 8px; + overflow: hidden; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); + } + .sector-badge { + margin-right: 4px; + margin-bottom: 4px; + } + .empty-state-icon { + font-size: 3rem; + opacity: 0.5; + } + </style> </head> -<body> +<body class="bg-light"> -<div class="container mt-4"> - <h2 class="text-center mb-4">Liste des Offres d'Emploi</h2> - - <div th:if="${successMessage}" class="alert alert-success"> - <p th:text="${successMessage}"></p> - </div> - <div th:if="${errorMessage}" class="alert alert-danger"> - <p th:text="${errorMessage}"></p> +<div class="container py-4"> + <!-- Header Section --> + <div class="d-flex justify-content-between align-items-center mb-4"> + <h2 class="fw-bold text-black mb-0"> + <i class="bi bi-briefcase me-2"></i> Job Offers + </h2> + <a th:href="@{/jobs/create}" class="btn btn-outline-secondary" th:if="${session != null and session.userType == 'Company'}"> + <i class="bi bi-plus-circle"></i> Create Job Offer + </a> </div> - <table class="table table-striped table-bordered table-hover"> - <thead class="table-dark"> - <tr> - <th>ID</th> - <th>Titre</th> - <th>Entreprise</th> - <th>Secteurs</th> - <th>Actions</th> - </tr> - </thead> - <tbody> - <tr th:each="job : ${jobOffers}"> - <td th:text="${job.id}"></td> - <td th:text="${job.title}"></td> - <td th:text="${job.company.denomination}"></td> - <td> - <ul class="list-unstyled"> - <li th:each="sector : ${job.sectors}" th:text="${sector.label}"></li> - </ul> - </td> - <td> - <a th:href="@{'/jobs/view/' + ${job.id}}" class="btn btn-primary btn-sm">Voir</a> - - <!-- Vérification si l'utilisateur connecté est une entreprise ET qu'il est le propriétaire de l'offre --> - <th:block th:if="${session.loggedInUser != null - and session.loggedInUser.usertype == 'company' - and session.loggedInUser.mail == job.company.mail}"> - <a th:href="@{'/jobs/' + ${job.id} + '/edit'}" class="btn btn-warning btn-sm">Modifier</a> - <a th:href="@{'/jobs/delete/' + ${job.id}}" - class="btn btn-danger btn-sm" - onclick="return confirm('Êtes-vous sûr de vouloir supprimer cette offre ?');"> - Supprimer - </a> - </th:block> - <a th:href="@{'/jobs/' + ${job.id} + '/edit'}" class="btn btn-warning btn-sm">Modifier</a> - <a th:href="@{'/jobs/delete/' + ${job.id}}" - class="btn btn-danger btn-sm" - onclick="return confirm('Êtes-vous sûr de vouloir supprimer cette offre ?');"> - Supprimer - </a> - </th:block> - </td> - </tr> - </tbody> - - - </table> - <div class="text-center mt-4"> - <a href="/jobs/create" class="btn btn-success">Créer une nouvelle offre</a> + <!-- Alert Messages --> + <div th:if="${successMessage}" class="alert alert-success alert-dismissible fade show"> + <i class="bi bi-check-circle-fill me-2"></i> + <span th:text="${successMessage}"></span> + <button type="button" class="btn-close" data-bs-dismiss="alert"></button> </div> - - <th:block th:if="${session.usertype != null and session.usertype == 'company'}"> - <a href="/jobs/create" class="btn btn-success">Créer une nouvelle offre</a> -</th:block> + <!-- Job Offers Table --> + <div class="card shadow-sm table-container"> + <div class="card-body p-0"> + <div class="table-responsive"> + <table class="table table-hover align-middle mb-0" th:if="${not #lists.isEmpty(jobOffers)}"> + <thead class="table-light"> + <tr> + <th class="ps-4">ID</th> + <th>Title</th> + <th>Company</th> + <th>Sectors</th> + <th>Qualification</th> + <th>Posted Date</th> + <th class="text-end pe-4">Actions</th> + </tr> + </thead> + <tbody> + <tr th:each="job : ${jobOffers}" class="job-card"> + <td class="ps-4" th:text="${job.id}"></td> + <td class="fw-semibold" th:text="${job.title}"></td> + <td th:text="${job.company?.denomination} ?: 'N/A'"></td> + <td> + <div class="d-flex flex-wrap"> + <span th:each="sector : ${job.sectors}" + class="badge favorite_back sector-badge" + th:text="${sector.label}"></span> + </div> + </td> + <td th:text="${job.qualificationLevel.label}"></td> + <td th:text="${job.publicationDate != null} ? ${#temporals.format(job.publicationDate, 'yyyy-MM-dd')} : 'N/A'"></td> <td class="text-end pe-4"> + <div class="d-flex gap-2 justify-content-end"> + <a th:href="@{'/jobs/view/' + ${job.id}}" + class="btn btn-sm favorite_outline"> + <i class="bi bi-eye"></i> View + </a> + <th:block th:if="${session != null and session.userType == 'Company' and session.loggedInUser.id == job.company.id}"> + <a th:href="@{'/jobs/edit/' + ${job.id}}" + class="btn btn-sm btn-outline-warning"> + <i class="bi bi-pencil"></i> Edit + </a> + <a th:href="@{'/jobs/delete/' + ${job.id}}" + class="btn btn-sm btn-outline-danger" + onclick="return confirm('Are you sure you want to delete this job offer?');"> + <i class="bi bi-trash"></i> Delete + </a> + </th:block> + </div> + </td> + </tr> + </tbody> + </table> + + <!-- Empty State --> + <div th:if="${#lists.isEmpty(jobOffers)}" class="text-center py-5"> + <i class="bi bi-briefcase empty-state-icon text-muted"></i> + <h4 class="mt-3 text-muted">No job offers found</h4> + </div> + </div> + </div> + </div> - </div> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> </body> +</section> </html> - @@ -137,8 +175,31 @@ <a href="/jobs/create" class="btn btn-success">Créer une nouvelle offre</a> <a th:href="@{/}" class="btn btn-secondary mt-3">🏠 Retour à l'accueil</a> + </div> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> </body> -</html> --> +</html> + <td> + <a th:href="@{'/jobs/view/' + ${job.id}}" class="btn btn-primary btn-sm">Voir</a> + +<th:block th:if="${session.loggedInUser != null + and session.loggedInUser.usertype == 'COMPANY' + and session.loggedInUser.mail == job.company.mail}"> + <a th:href="@{'/jobs/' + ${job.id} + '/edit'}" class="btn btn-warning btn-sm">Modifier</a> + <a th:href="@{'/jobs/delete/' + ${job.id}}" + class="btn btn-danger btn-sm" + onclick="return confirm('Êtes-vous sûr de vouloir supprimer cette offre ?');"> + Supprimer + </a> +</th:block> +<a th:href="@{'/jobs/' + ${job.id} + '/edit'}" class="btn btn-warning btn-sm">Modifier</a> +<a th:href="@{'/jobs/delete/' + ${job.id}}" + class="btn btn-danger btn-sm" + onclick="return confirm('Êtes-vous sûr de vouloir supprimer cette offre ?');"> + Supprimer +</a> +</th:block> +</td> + --> diff --git a/target/classes/templates/jobOffer/jobOfferView.html b/target/classes/templates/jobOffer/jobOfferView.html index e5fbffb..bd9bc09 100644 --- a/target/classes/templates/jobOffer/jobOfferView.html +++ b/target/classes/templates/jobOffer/jobOfferView.html @@ -1,55 +1,94 @@ <!DOCTYPE html> -<html xmlns:th="http://www.thymeleaf.org"> +<html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> <head> - <title>Détails de l'Offre d'Emploi</title> + <title>Job Details</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"> + <style> + :root { + --primary: #072262; + --accent: #ff6b35; + } + </style> </head> -<body class="bg-light"> +<body> +<section> + <header class="mb-4"> + <h2 class="fw-bold border-bottom pb-2">Job Details</h2> + </header> -<div class="container mt-5"> - <h2 class="text-center mb-4"> Détails de l'Offre d'Emploi</h2> + <!-- Alert Messages --> + <div th:if="${successMessage}" class="alert alert-success alert-dismissible fade show" role="alert"> + <i class="bi bi-check-circle-fill me-2"></i> + <span th:text="${successMessage}"></span> + <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> + </div> - <div class="card shadow-lg"> - <div class="card-header bg-primary text-white text-center"> - <h4 th:text="${jobOffer.title}"></h4> - </div> + <div th:if="${errorMessage}" class="alert alert-danger alert-dismissible fade show" role="alert"> + <i class="bi bi-exclamation-triangle-fill me-2"></i> + <span th:text="${errorMessage}"></span> + <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> + </div> + + <div th:if="${job != null}" class="card shadow-sm mb-4"> <div class="card-body"> - <p><strong>ID de l'offre :</strong> <span th:text="${jobOffer.id}"></span></p> - <p><strong>Description :</strong> <span th:text="${jobOffer.taskDescription}"></span></p> - <p><strong>Entreprise :</strong> <span th:text="${jobOffer.company.denomination}"></span></p> - <p><strong>Date de publication :</strong> <span th:text="${jobOffer.publicationDate}"></span></p> - - <!-- ✅ Ajout du Niveau de Qualification --> - <p><strong>Niveau de qualification requis :</strong> - <span th:text="${jobOffer.qualificationLevel != null ? jobOffer.qualificationLevel.label : 'Non spécifié'}"></span> - </p> - - <!-- ✅ Section Secteurs améliorée avec des badges --> - <h5 class="mt-3">Secteurs :</h5> - <div class="d-flex flex-wrap"> - <span th:each="sector : ${jobOffer.sectors}" - th:text="${sector.label}" - class="badge bg-success text-white m-1 p-2"> - </span> + <div class="row g-3"> + <!-- Left Column --> + <div class="col-md-6"> + <h4 class="favorite_color fw-bold mb-3"> + <i class="bi bi-briefcase"></i> Job Information + </h4> + + <div class="mb-3"> + <label class="form-label fw-semibold">Job Title</label> + <div class="form-control-plaintext bg-light p-2 rounded" th:text="${job.title}"></div> + </div> + + <div class="mb-3"> + <label class="form-label fw-semibold">Company</label> + <div class="form-control-plaintext bg-light p-2 rounded" + th:text="${job.company?.denomination} ?: 'N/A'"></div> + </div> + + <div class="mb-3"> + <label class="form-label fw-semibold">Qualification Level</label> + <div class="form-control-plaintext bg-light p-2 rounded" + th:text="${job.qualificationLevel.label}"></div> + </div> + </div> + + <!-- Right Column --> + <div class="col-md-6"> + <h4 class="favorite_color fw-bold mb-3"> + <i class="bi bi-calendar-event"></i> Additional Information + </h4> + + <div class="mb-3"> + <label class="form-label fw-semibold">Publication Date</label> + <div class="form-control-plaintext bg-light p-2 rounded" + th:text="${#temporals.format(job.publicationDate, 'MMMM d, yyyy')}"></div> + </div> + + <div th:if="${not #lists.isEmpty(job.sectors)}" class="mb-3"> + <label class="form-label fw-semibold">Sectors</label> + <div class="form-control-plaintext bg-light p-2 rounded"> + <span th:each="sector : ${job.sectors}" class="badge favorite_back me-1" + th:text="${sector.label}"></span> + </div> + </div> + </div> </div> - </div> - </div> - <div class="text-center mt-4"> - <a href="/jobs" class="btn btn-secondary btn-lg"> - ⬅ <i class="bi bi-arrow-left"></i> Retour - </a> - <a th:href="@{'/jobs/' + ${jobOffer.id} + '/edit'}" class="btn btn-warning btn-lg"> - ✏ <i class="bi bi-pencil-square"></i> Modifier - </a> - <a th:href="@{'/jobs/delete/' + ${jobOffer.id}}" - class="btn btn-danger btn-lg" - onclick="return confirm('Êtes-vous sûr de vouloir supprimer cette offre ?');"> - 🗑 <i class="bi bi-trash"></i> Supprimer - </a> + <!-- Job Description Section --> + <div class="mt-4"> + <h4 class="favorite_color fw-bold mb-3"> + <i class="bi bi-file-text"></i> Job Description + </h4> + <div class="form-control-plaintext bg-light p-3 rounded" th:utext="${job.taskDescription}"></div> + </div> + </div> </div> -</div> -<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> +</section> </body> -</html> +</html> \ No newline at end of file diff --git a/target/classes/templates/login.html b/target/classes/templates/login.html index fbaa5e7..658fa74 100644 --- a/target/classes/templates/login.html +++ b/target/classes/templates/login.html @@ -1,45 +1,161 @@ <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" th:fragment="article(subcontent)" - th:replace="~{/baseTemplate/base :: layout(~{},~{::section})}"> - -<section class="modal modal-sheet position-static d-block bg-body-secondary p-4 py-md-5" tabindex="-1"> - <div class="modal-dialog" role="document"> - <div class="modal-content rounded-4 shadow"> - <header class="modal-header p-5 pb-4 border-bottom-0"> - <h1 class="fw-bold mb-0 fs-2">Sign in</h1> - <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> - </header> - - <article class="modal-body p-5 pt-0"> - <small class="text-secondary">Log in by entering your email address and password.</small> - <form action="/login" method="post"> - <fieldset class="mb-3 form-floating"> - <input type="email" id="uid" class="form-control rounded-3" name="mail" autofocus="autofocus" required - placeholder="name@domain.com" /> - <label for="uid">Email address</label> - </fieldset> - <fieldset class="mb-3 form-floating"> - <input type="password" id="idpwd" class="form-control rounded-3" name="password" placeholder="your password" - required /> - <label for="idpwd">Password</label> - </fieldset> - <input type="submit" value="Sign in" class="w-100 mb-2 btn btn-lg rounded-3 btn-primary" /> - </form> - - <!-- Affichage des erreurs de connexion --> - <div th:if="${error}" class="alert alert-danger mt-3" role="alert"> - <p th:text="${error}"></p> + th:replace="~{/baseTemplate/base :: layout(~{},~{::section})}"> + +<head> + <style> + .favorite_color { + color:rgb(0, 183, 211); + } + + .favorite_back { + background-color: rgb(0, 183, 211); + } + + .favorite_outline { + border-color: rgb(0, 183, 211); + color: rgb(0, 183, 211); + } + + .favorite_outline:hover { + background-color: rgb(0, 183, 211); + color: white; + } + + .login-card { + border: none; + border-radius: 15px; + box-shadow: 0 10px 30px rgba(7, 34, 98, 0.1); + overflow: hidden; + } + + .card-header { + background-color: rgb(0, 183, 211); + color: white; + padding: 1.5rem; + } + + .form-control { + border-radius: 8px; + padding: 12px 15px; + border: 1px solid #e0e0e0; + transition: all 0.3s; + } + + .form-control:focus { + border-color:rgb(0, 183, 211); + box-shadow: 0 0 0 0.25rem rgba(7, 34, 98, 0.25); + } + + .floating-label { + color: #6c757d; + transition: all 0.3s; + } + + .form-floating>.form-control:focus~label, + .form-floating>.form-control:not(:placeholder-shown)~label { + color:rgb(0, 183, 211); + transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); + } + + .login-btn { + padding: 12px; + font-size: 1rem; + letter-spacing: 0.5px; + transition: all 0.3s; + } + + .login-btn:hover { + background-color: rgb(0, 183, 211); + transform: translateY(-2px); + } + + .divider { + display: flex; + align-items: center; + margin: 1.5rem 0; + } + + .divider::before, + .divider::after { + content: ""; + flex: 1; + border-bottom: 1px solid #e0e0e0; + } + + .divider-text { + padding: 0 1rem; + color: #6c757d; + font-size: 0.9rem; + } + + .register-option { + transition: all 0.3s; + padding: 10px; + border-radius: 8px; + } + + .register-option:hover { + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(7, 34, 98, 0.1); + } + + .register-icon { + margin-right: 8px; + } + </style> +</head> + +<section class="container my-5"> + <div class="row justify-content-center"> + <div class="col-md-6 col-lg-5"> + <div class="card login-card"> + <div class="card-header favorite_back text-white text-center"> + <h3 class="mb-0">Welcome Back</h3> + <p class="mb-0 opacity-75">Sign in to your account</p> + </div> + + <div class="card-body p-4"> + <form th:action="@{/login}" method="post"> + <fieldset class="mb-3 form-floating"> + <input type="email" id="uid" class="form-control" name="mail" autofocus="autofocus" required + placeholder="name@domain.com" /> + <label for="uid" class="floating-label">Email address</label> + </fieldset> + + <fieldset class="mb-4 form-floating"> + <input type="password" id="idpwd" class="form-control" name="password" placeholder="your password" + required /> + <label for="idpwd" class="floating-label">Password</label> + </fieldset> + + <input type="submit" value="Sign in" class="w-100 btn login-btn text-black favorite_outline" /> + </form> + + <!-- Affichage des erreurs de connexion --> + <div th:if="${error}" class="alert alert-danger mt-3" role="alert"> + <p class="mb-0" th:text="${error}"></p> + </div> </div> - </article> - - <footer class="modal-body p-5 pt-0"> - <hr class="my-4"> - <a th:href="@{/companies/create}" class="btn w-100 mb-2 btn btn-lg rounded-3 btn-secondary">Sign up as company</a> - <a th:href="@{/candidates/signup}" class="btn w-100 mb-2 btn btn-lg rounded-3 btn-secondary">Sign up as candidate</a> - <small class="text-secondary">By clicking Sign up, you agree to the terms of use.</small> - </footer> + + <div class="px-4 pb-4"> + <div class="divider"> + <span class="divider-text">OR</span> + </div> + + <p class="mb-3 text-center">Don't have an account?</p> + + <div class="d-grid gap-3"> + <a th:href="@{/companies/create}" class="btn favorite_outline register-option"> + <i class="bi bi-building register-icon "></i> Register as Company + </a> + <a th:href="@{/candidates/signup}" class="btn favorite_outline register-option"> + <i class="bi bi-person register-icon"></i> Register as Candidate + </a> + </div> + </div> + </div> </div> </div> </section> - -</html> +</html> \ No newline at end of file diff --git a/target/classes/templates/qualificationLevel/qualificationLevelList.html b/target/classes/templates/qualificationLevel/qualificationLevelList.html index c97eb59..edadb95 100644 --- a/target/classes/templates/qualificationLevel/qualificationLevelList.html +++ b/target/classes/templates/qualificationLevel/qualificationLevelList.html @@ -1,45 +1,303 @@ <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> -<title>Qualification Level</title> +<title>Qualification Levels</title> <section> - <header> - <h1>List of qualification levels</h1> - </header> - <article> - <!-- <a href="javascript:history.back()"><img th:src="@{/img/back.png}" alt="Back"/></a> --> - <p th:if="${#lists.size(qualificationlevellist)} == 0">No qualification level defined yet.</p> - <th:block th:if="${#lists.size(qualificationlevellist)} > 0"> - <table class="table table-striped"> - <caption>List of qualification levels</caption> - <thead> - <tr> - <th scope="col">#</th> - <th scope="col">Label</th> - </tr> - </thead> - <tbody> - <tr th:each="ql : ${qualificationlevellist}"> - <th scope="row" th:text="${ql.id}" /> - <td th:text="${ql.label}" /> - <!-- <td th:if="${#ctx.session.uid} != null AND ${#ctx.session.logintype} == 'adm'" class="nav-item active"> - <a th:href="@{/deletequalificationlevel/{id}(id=${ql.id})}"> - <img th:src="@{img/minus.png}" alt="Delete this sector" class="minilogo"/> - </a> - </td> --> - </tr> - </tbody> - </table> - </th:block> - <!-- <div th:if="${#ctx.session.uid} != null AND ${#ctx.session.logintype} == 'adm'" class="row h-10"> - <form action="/addqualificationlevel" method="get" class="col-xs-12 col-sm-6 col-md-4 col-lg-2"> - <label for="labelql">Label</label> - <input type="text" id="labelql" name="labelql" autofocus="autofocus" minlength="3" required/> <br /> - - <input type="submit" value="Add" /> - </form> - </form> - </div> --> - </article> -</section> + <head> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"> + <style> + .qualification-card { + transition: all 0.3s ease; + border-left: 4px solid transparent; + } + .qualification-card:hover { + transform: translateY(-2px); + border-left-color: #3a7bd5; + background-color: #f8f9fa; + } + .table-container { + border-radius: 8px; + overflow: hidden; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); + } + .empty-state { + min-height: 300px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + .search-container { + position: relative; + margin-bottom: 1.5rem; + } + .search-icon { + position: absolute; + left: 12px; + top: 10px; + color: #6c757d; + } + .search-input { + padding-left: 40px; + border-radius: 20px; + border: 1px solid #ced4da; + transition: all 0.3s; + } + .search-input:focus { + box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); + border-color: #86b7fe; + } + .hidden-row { + display: none; + } + .pagination .page-item.active .page-link { + background-color: rgb(0, 184, 222); + border-color: rgb(0, 184, 222); + color: white; + } + .pagination .page-link { + color: rgb(0, 184, 222); + } + .page-size-selector { + width: auto; + display: inline-block; + } + </style> + </head> + <body class="bg-light"> + <div class="container py-4"> + <!-- Header Section --> + <div class="d-flex justify-content-between align-items-center mb-4"> + <h2 class="fw-bold text-black mb-0"> + <i class="bi bi-award me-2"></i> Qualification Levels + </h2> + </div> + + <!-- Search and Page Size Controls --> + <div class="row mb-3"> + <div class="col-md-6"> + <div class="search-container"> + <i class="bi bi-search search-icon"></i> + <input type="text" class="form-control search-input" id="liveSearchInput" + placeholder="Start typing to filter qualification levels..." autocomplete="off"> + </div> + </div> + <div class="col-md-6 text-md-end"> + <div class="d-inline-flex align-items-center"> + <span class="me-2">Show:</span> + <select class="form-select form-select-sm page-size-selector" id="pageSizeSelect"> + <option value="5">5</option> + <option value="10" selected>10</option> + <option value="20">20</option> + <option value="50">50</option> + <option value="100">100</option> + </select> + <span class="ms-2">entries</span> + </div> + </div> + </div> + + <!-- Qualification Levels Table --> + <div class="card shadow-sm table-container mb-3"> + <div class="card-body p-0"> + <div class="table-responsive"> + <table class="table table-hover align-middle mb-0" id="qualificationsTable"> + <thead class="table-light"> + <tr> + <th>Label</th> + <th class="text-end pe-4" th:if="${session != null and session.logintype == 'adm'}">Actions</th> + </tr> + </thead> + <tbody> + <tr th:each="ql : ${qualificationlevellist}" class="qualification-card"> + <td class="fw-semibold qualification-label" th:text="${ql.label}"></td> + <td class="text-end pe-4" th:if="${session != null and session.logintype == 'adm'}"> + <div class="d-flex gap-2 justify-content-end"> + <a th:href="@{/deletequalificationlevel/{id}(id=${ql.id})}" + class="btn btn-sm btn-outline-danger"> + <i class="bi bi-trash"></i> Delete + </a> + </div> + </td> + </tr> + </tbody> + </table> + + <!-- Empty State (initial) --> + <div th:if="${#lists.isEmpty(qualificationlevellist)}" class="empty-state text-center py-5"> + <i class="bi bi-award text-muted" style="font-size: 3rem;"></i> + <h4 class="mt-3 text-muted">No qualification levels defined</h4> + </div> + + <!-- Empty State (after filtering) --> + <div id="noResultsState" class="empty-state text-center py-5" style="display: none;"> + <i class="bi bi-search text-muted" style="font-size: 3rem;"></i> + <h4 class="mt-3 text-muted">No matching qualifications found</h4> + <p class="text-muted">Try adjusting your search</p> + </div> + </div> + </div> + </div> + + <!-- Pagination and Info --> + <div class="row"> + <div class="col-md-6"> + <div id="pageInfo" class="text-muted">Showing 1 to 10 of <span id="totalRecords">0</span> entries</div> + </div> + <div class="col-md-6"> + <nav aria-label="Page navigation" class="float-md-end"> + <ul class="pagination" id="pagination"> + <li class="page-item disabled" id="prevPage"> + <a class="page-link" href="#" aria-label="Previous"> + <span aria-hidden="true">«</span> + </a> + </li> + <!-- Pages will be inserted here by JavaScript --> + <li class="page-item" id="nextPage"> + <a class="page-link" href="#" aria-label="Next"> + <span aria-hidden="true">»</span> + </a> + </li> + </ul> + </nav> + </div> + </div> + </div> + + <script> + document.addEventListener('DOMContentLoaded', function() { + const searchInput = document.getElementById('liveSearchInput'); + const tableRows = document.querySelectorAll('#qualificationsTable tbody tr'); + const noResultsState = document.getElementById('noResultsState'); + const initialEmptyState = document.querySelector('.empty-state'); + const pageSizeSelect = document.getElementById('pageSizeSelect'); + const pagination = document.getElementById('pagination'); + const prevPage = document.getElementById('prevPage'); + const nextPage = document.getElementById('nextPage'); + const pageInfo = document.getElementById('pageInfo'); + const totalRecords = document.getElementById('totalRecords'); + + let currentPage = 1; + let pageSize = parseInt(pageSizeSelect.value); + let filteredRows = Array.from(tableRows); + + // Initialize + updateTable(); + updatePagination(); + + // Search functionality + searchInput.addEventListener('input', function() { + const searchTerm = this.value.toLowerCase(); + filteredRows = Array.from(tableRows).filter(row => { + const qualLabel = row.querySelector('.qualification-label').textContent.toLowerCase(); + return qualLabel.includes(searchTerm); + }); + + currentPage = 1; + updateTable(); + updatePagination(); + }); + + // Page size change + pageSizeSelect.addEventListener('change', function() { + pageSize = parseInt(this.value); + currentPage = 1; + updateTable(); + updatePagination(); + }); + + // Pagination click handlers + pagination.addEventListener('click', function(e) { + e.preventDefault(); + if (e.target.closest('.page-link')) { + const target = e.target.closest('.page-link'); + if (target.getAttribute('aria-label') === 'Previous') { + if (currentPage > 1) currentPage--; + } else if (target.getAttribute('aria-label') === 'Next') { + if (currentPage < Math.ceil(filteredRows.length / pageSize)) currentPage++; + } else { + currentPage = parseInt(target.textContent); + } + updateTable(); + updatePagination(); + } + }); + function updateTable() { + const start = (currentPage - 1) * pageSize; + const end = start + pageSize; + const visibleRows = filteredRows.slice(start, end); + + // Hide all rows first + tableRows.forEach(row => { + row.classList.add('hidden-row'); + }); + + // Show only visible rows + visibleRows.forEach(row => { + row.classList.remove('hidden-row'); + }); + + // Update empty states + if (filteredRows.length === 0) { + noResultsState.style.display = 'flex'; + if (initialEmptyState) initialEmptyState.style.display = 'none'; + } else { + noResultsState.style.display = 'none'; + if (initialEmptyState) initialEmptyState.style.display = 'none'; + } + } + + function updatePagination() { + const totalPages = Math.ceil(filteredRows.length / pageSize); + totalRecords.textContent = filteredRows.length; + + // Update page info + const start = (currentPage - 1) * pageSize + 1; + const end = Math.min(currentPage * pageSize, filteredRows.length); + pageInfo.textContent = `Showing ${start} to ${end} of ${filteredRows.length} entries`; + + // Clear existing page numbers + const pageNumbers = pagination.querySelectorAll('.page-number'); + pageNumbers.forEach(el => el.remove()); + + // Add new page numbers + const maxVisiblePages = 5; + let startPage, endPage; + + if (totalPages <= maxVisiblePages) { + startPage = 1; + endPage = totalPages; + } else { + const maxVisibleBeforeCurrent = Math.floor(maxVisiblePages / 2); + const maxVisibleAfterCurrent = Math.ceil(maxVisiblePages / 2) - 1; + + if (currentPage <= maxVisibleBeforeCurrent) { + startPage = 1; + endPage = maxVisiblePages; + } else if (currentPage + maxVisibleAfterCurrent >= totalPages) { + startPage = totalPages - maxVisiblePages + 1; + endPage = totalPages; + } else { + startPage = currentPage - maxVisibleBeforeCurrent; + endPage = currentPage + maxVisibleAfterCurrent; + } + } + + // Add page number items + for (let i = startPage; i <= endPage; i++) { + const pageItem = document.createElement('li'); + pageItem.className = `page-item page-number ${i === currentPage ? 'active' : ''}`; + pageItem.innerHTML = `<a class="page-link" href="#">${i}</a>`; + nextPage.parentNode.insertBefore(pageItem, nextPage); + } + + // Update prev/next buttons + prevPage.classList.toggle('disabled', currentPage === 1); + nextPage.classList.toggle('disabled', currentPage === totalPages || totalPages === 0); + } + }); + </script> + </body> +</section> </html> \ No newline at end of file diff --git a/target/classes/templates/sector/sectorList.html b/target/classes/templates/sector/sectorList.html index b30d471..cb3e68a 100644 --- a/target/classes/templates/sector/sectorList.html +++ b/target/classes/templates/sector/sectorList.html @@ -2,48 +2,307 @@ <html xmlns:th="http://www.thymeleaf.org" th:replace="~{/baseTemplate/base :: layout(~{::title},~{::section})}"> <title>Sectors</title> <section> - <header> - <h1>List of sectors</h1> - </header> - <article> - <p th:if="${#lists.size(sectorlist)} == 0">No sector defined yet.</p> - - <th:block th:if="${#lists.size(sectorlist)} > 0"> - - <table class="table table-striped"> - <caption>List of sectors</caption> - <thead> - <tr> - <th scope="col">#</th> - <th scope="col">Label</th> - </tr> - </thead> - <tbody> - <tr th:each="sec : ${sectorlist}"> - <th scope="row" th:text="${sec.id}" /> - <td th:text="${sec.label}" /> - <td th:if="${#ctx.session.uid} != null AND ${#ctx.session.logintype} == 'adm'" - class="nav-item active"> - <a th:href="@{/deletesector/{id}(id=${sec.id})}"> - <img th:src="@{img/minus.png}" alt="Delete this sector" class="minilogo" /> + <head> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"> + <style> + .sector-card { + transition: all 0.3s ease; + border-left: 4px solid transparent; + } + .sector-card:hover { + transform: translateY(-2px); + border-left-color: #00B8DEFF; + background-color: #f8f9fa; + } + .table-container { + border-radius: 8px; + overflow: hidden; + box-shadow: 0 1px 3px rgba(0,0,0,0.1); + } + .empty-state { + min-height: 300px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + .search-container { + position: relative; + margin-bottom: 1.5rem; + } + .search-icon { + position: absolute; + left: 12px; + top: 10px; + color: #6c757d; + } + .search-input { + padding-left: 40px; + border-radius: 20px; + border: 1px solid #ced4da; + transition: all 0.3s; + } + .search-input:focus { + box-shadow: 0 0 0 0.25rem rgb(0, 184, 222); + border-color: #00B8DEFF; + + } + .hidden-row { + display: none; + } + .pagination .page-item.active .page-link { + background-color: #00B8DEFF; + border-color: #00B8DEFF; + color: #ffffff; + } + .pagination .page-link { + color: #00B8DEFF; + } + .page-size-selector { + width: auto; + display: inline-block; + } + .controls-row { + margin-bottom: 1.5rem; + } + </style> + </head> + <body class="bg-light"> + <div class="container py-4"> + <!-- Header Section --> + <div class="d-flex justify-content-between align-items-center mb-4"> + <h2 class="fw-bold text-black mb-0"> + <i class="bi bi-tags me-2"></i> Sectors + </h2> + </div> + + <!-- Search and Page Size Controls --> + <div class="row controls-row"> + <div class="col-md-6"> + <div class="search-container"> + <i class="bi bi-search search-icon"></i> + <input type="text" class="form-control search-input" id="liveSearchInput" + placeholder="Start typing to filter sectors..." autocomplete="off"> + </div> + </div> + <div class="col-md-6 text-md-end"> + <div class="d-inline-flex align-items-center"> + <span class="me-2">Show:</span> + <select class="form-select form-select-sm page-size-selector" id="pageSizeSelect"> + <option value="5">5</option> + <option value="10" selected>10</option> + <option value="20">20</option> + <option value="50">50</option> + <option value="100">100</option> + </select> + <span class="ms-2">entries</span> + </div> + </div> + </div> + + <!-- Sectors Table --> + <div class="card shadow-sm table-container mb-3"> + <div class="card-body p-0"> + <div class="table-responsive"> + <table class="table table-hover align-middle mb-0" id="sectorsTable"> + <thead class="table-light"> + <tr> + <th>Label</th> + <th class="text-end pe-4">Actions</th> + </tr> + </thead> + <tbody> + <tr th:each="sec : ${sectorlist}" class="sector-card"> + <td class="fw-semibold sector-label" th:text="${sec.label}"></td> + <td class="text-end pe-4"> + <div class="d-flex gap-2 justify-content-end"> + <a th:href="@{/deletesector/{id}(id=${sec.id})}" + class="btn btn-sm btn-outline-danger" + th:if="${session != null and session.logintype == 'adm'}"> + <i class="bi bi-trash"></i> Delete + </a> + </div> + </td> + </tr> + </tbody> + </table> + + <!-- Empty State (initial) --> + <div th:if="${#lists.isEmpty(sectorlist)}" class="empty-state text-center py-5"> + <i class="bi bi-tag text-muted" style="font-size: 3rem;"></i> + <h4 class="mt-3 text-muted">No sectors defined</h4> + </div> + + <!-- Empty State (after filtering) --> + <div id="noResultsState" class="empty-state text-center py-5" style="display: none;"> + <i class="bi bi-search text-muted" style="font-size: 3rem;"></i> + <h4 class="mt-3 text-muted">No matching sectors found</h4> + <p class="text-muted">Try adjusting your search</p> + </div> + </div> + </div> + </div> + + <!-- Pagination and Info --> + <div class="row"> + <div class="col-md-6"> + <div id="pageInfo" class="text-muted">Showing 1 to 10 of <span id="totalRecords">0</span> entries</div> + </div> + <div class="col-md-6"> + <nav aria-label="Page navigation" class="float-md-end"> + <ul class="pagination" id="pagination"> + <li class="page-item disabled" id="prevPage"> + <a class="page-link" href="#" aria-label="Previous"> + <span aria-hidden="true">«</span> </a> - </td> - </tr> - </tbody> - </table> - </th:block> - - <aside th:if="${#ctx.session.uid} != null AND ${#ctx.session.logintype} == 'adm'" class="row h-10"> - <form action="/addsector" method="get" class="col-xs-12 col-sm-6 col-md-4 col-lg-2"> - <fieldset> - <label for="labelsector">Label</label> - <input type="text" id="labelsector" name="labelsector" autofocus="autofocus" minlength="3" - required /> - </fieldset> - <input type="submit" value="Add" /> - </form> - </aside> - </article> -</section> + </li> + <!-- Pages will be inserted here by JavaScript --> + <li class="page-item" id="nextPage"> + <a class="page-link" href="#" aria-label="Next"> + <span aria-hidden="true">»</span> + </a> + </li> + </ul> + </nav> + </div> + </div> + </div> + + <script> + document.addEventListener('DOMContentLoaded', function() { + const searchInput = document.getElementById('liveSearchInput'); + const tableRows = document.querySelectorAll('#sectorsTable tbody tr'); + const noResultsState = document.getElementById('noResultsState'); + const initialEmptyState = document.querySelector('.empty-state'); + const pageSizeSelect = document.getElementById('pageSizeSelect'); + const pagination = document.getElementById('pagination'); + const prevPage = document.getElementById('prevPage'); + const nextPage = document.getElementById('nextPage'); + const pageInfo = document.getElementById('pageInfo'); + const totalRecords = document.getElementById('totalRecords'); + + let currentPage = 1; + let pageSize = parseInt(pageSizeSelect.value); + let filteredRows = Array.from(tableRows); + + // Initialize + updateTable(); + updatePagination(); + + // Search functionality + searchInput.addEventListener('input', function() { + const searchTerm = this.value.toLowerCase(); + filteredRows = Array.from(tableRows).filter(row => { + const sectorLabel = row.querySelector('.sector-label').textContent.toLowerCase(); + return sectorLabel.includes(searchTerm); + }); + + currentPage = 1; + updateTable(); + updatePagination(); + }); + + // Page size change + pageSizeSelect.addEventListener('change', function() { + pageSize = parseInt(this.value); + currentPage = 1; + updateTable(); + updatePagination(); + }); + // Pagination click handlers + pagination.addEventListener('click', function(e) { + e.preventDefault(); + if (e.target.closest('.page-link')) { + const target = e.target.closest('.page-link'); + if (target.getAttribute('aria-label') === 'Previous') { + if (currentPage > 1) currentPage--; + } else if (target.getAttribute('aria-label') === 'Next') { + if (currentPage < Math.ceil(filteredRows.length / pageSize)) currentPage++; + } else { + currentPage = parseInt(target.textContent); + } + updateTable(); + updatePagination(); + } + }); + + function updateTable() { + const start = (currentPage - 1) * pageSize; + const end = start + pageSize; + const visibleRows = filteredRows.slice(start, end); + + // Hide all rows first + tableRows.forEach(row => { + row.classList.add('hidden-row'); + }); + + // Show only visible rows + visibleRows.forEach(row => { + row.classList.remove('hidden-row'); + }); + + // Update empty states + if (filteredRows.length === 0) { + noResultsState.style.display = 'flex'; + if (initialEmptyState) initialEmptyState.style.display = 'none'; + } else { + noResultsState.style.display = 'none'; + if (initialEmptyState) initialEmptyState.style.display = 'none'; + } + } + + function updatePagination() { + const totalPages = Math.ceil(filteredRows.length / pageSize); + totalRecords.textContent = filteredRows.length; + + // Update page info + const start = (currentPage - 1) * pageSize + 1; + const end = Math.min(currentPage * pageSize, filteredRows.length); + pageInfo.textContent = `Showing ${start} to ${end} of ${filteredRows.length} entries`; + + // Clear existing page numbers + const pageNumbers = pagination.querySelectorAll('.page-number'); + pageNumbers.forEach(el => el.remove()); + + // Add new page numbers + const maxVisiblePages = 5; + let startPage, endPage; + + if (totalPages <= maxVisiblePages) { + startPage = 1; + endPage = totalPages; + } else { + const maxVisibleBeforeCurrent = Math.floor(maxVisiblePages / 2); + const maxVisibleAfterCurrent = Math.ceil(maxVisiblePages / 2) - 1; + + if (currentPage <= maxVisibleBeforeCurrent) { + startPage = 1; + endPage = maxVisiblePages; + } else if (currentPage + maxVisibleAfterCurrent >= totalPages) { + startPage = totalPages - maxVisiblePages + 1; + endPage = totalPages; + } else { + startPage = currentPage - maxVisibleBeforeCurrent; + endPage = currentPage + maxVisibleAfterCurrent; + } + } + + // Add page number items + for (let i = startPage; i <= endPage; i++) { + const pageItem = document.createElement('li'); + pageItem.className = `page-item page-number ${i === currentPage ? 'active' : ''}`; + pageItem.innerHTML = `<a class="page-link" href="#">${i}</a>`; + nextPage.parentNode.insertBefore(pageItem, nextPage); + } + + // Update prev/next buttons + prevPage.classList.toggle('disabled', currentPage === 1); + nextPage.classList.toggle('disabled', currentPage === totalPages || totalPages === 0); + } + }); + </script> + </body> +</section> </html> \ No newline at end of file diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst index 3531355..57d806f 100644 --- a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst @@ -10,6 +10,7 @@ fr\atlantique\imt\inf211\jobmngt\dao\ApplicationMessageDao.class fr\atlantique\imt\inf211\jobmngt\entity\Candidate.class fr\atlantique\imt\inf211\jobmngt\controller\LoginController.class fr\atlantique\imt\inf211\jobmngt\config\WebConfig.class +fr\atlantique\imt\inf211\jobmngt\config\CompanyUserDetailsService.class fr\atlantique\imt\inf211\jobmngt\service\JobOfferService.class fr\atlantique\imt\inf211\jobmngt\controller\ApplicationController.class fr\atlantique\imt\inf211\jobmngt\service\CandidateServiceImpl.class @@ -19,6 +20,7 @@ fr\atlantique\imt\inf211\jobmngt\dao\ApplicationDao.class fr\atlantique\imt\inf211\jobmngt\service\AppUserService.class fr\atlantique\imt\inf211\jobmngt\dao\AppUserDao.class fr\atlantique\imt\inf211\jobmngt\dao\CompanyDao.class +fr\atlantique\imt\inf211\jobmngt\config\SecurityConfig.class fr\atlantique\imt\inf211\jobmngt\entity\QualificationLevel.class fr\atlantique\imt\inf211\jobmngt\service\JobOfferServiceImpl.class fr\atlantique\imt\inf211\jobmngt\controller\PagesController.class @@ -30,6 +32,7 @@ fr\atlantique\imt\inf211\jobmngt\entity\JobOffer.class fr\atlantique\imt\inf211\jobmngt\service\ApplicationServiceImpl.class fr\atlantique\imt\inf211\jobmngt\converter\QualificationLevelConverter.class fr\atlantique\imt\inf211\jobmngt\service\QualificationLevelService.class +fr\atlantique\imt\inf211\jobmngt\entity\RoleType.class fr\atlantique\imt\inf211\jobmngt\service\AppUserServiceImpl.class fr\atlantique\imt\inf211\jobmngt\entity\Sector.class fr\atlantique\imt\inf211\jobmngt\service\CompanyService.class diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst index 6646ede..82fd195 100644 --- a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -1,44 +1,47 @@ -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\dao\OfferMessageDao.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\entity\AppUser.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\service\SectorServiceImpl.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\service\AppUserServiceImpl.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\service\ApplicationService.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\entity\ApplicationMessage.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\entity\Candidate.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\controller\PagesController.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\controller\JobOfferController.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\entity\Company.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\controller\CompanyController.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\dao\ApplicationDao.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\service\CandidateServiceImpl.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\entity\Application.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\controller\CandidateController.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\controller\QualificationLevelController.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\service\AppUserService.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\service\QualificationLevelServiceImpl.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\dao\QualificationLevelDao.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\config\WebConfig.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\entity\JobOffer.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\service\ApplicationServiceImpl.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\service\CompanyServiceImpl.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\controller\ApplicationController.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\service\SectorService.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\service\JobOfferService.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\service\JobOfferServiceImpl.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\dao\JobOfferDao.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\dao\CandidateDao.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\service\CompanyService.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\service\CandidateService.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\service\QualificationLevelService.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\converter\QualificationLevelConverter.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\entity\Sector.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\converter\CompanyConverter.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\JobmngtApplication.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\dao\ApplicationMessageDao.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\dao\CompanyDao.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\dao\SectorDao.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\dao\AppUserDao.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\entity\OfferMessage.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\entity\QualificationLevel.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\controller\LoginController.java -C:\Users\LENOVO\Desktop\JobManagement-1\JobmngmntVersionAFP\src\main\java\fr\atlantique\imt\inf211\jobmngt\controller\SectorController.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\service\AppUserService.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\service\CompanyServiceImpl.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\dao\ApplicationDao.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\entity\AppUser.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\config\WebConfig.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\dao\AppUserDao.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\service\CompanyService.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\controller\SectorController.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\dao\QualificationLevelDao.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\service\JobOfferServiceImpl.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\dao\CandidateDao.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\service\SectorService.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\controller\PagesController.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\entity\RoleType.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\service\ApplicationService.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\controller\CompanyController.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\converter\QualificationLevelConverter.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\JobmngtApplication.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\service\CandidateServiceImpl.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\service\QualificationLevelServiceImpl.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\entity\JobOffer.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\dao\OfferMessageDao.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\dao\JobOfferDao.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\service\CandidateService.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\controller\QualificationLevelController.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\entity\Candidate.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\service\SectorServiceImpl.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\dao\ApplicationMessageDao.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\entity\OfferMessage.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\dao\CompanyDao.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\service\AppUserServiceImpl.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\config\CompanyUserDetailsService.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\controller\ApplicationController.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\controller\CandidateController.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\dao\SectorDao.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\entity\ApplicationMessage.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\service\JobOfferService.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\entity\Sector.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\controller\LoginController.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\service\ApplicationServiceImpl.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\controller\JobOfferController.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\converter\CompanyConverter.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\service\QualificationLevelService.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\entity\Company.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\entity\QualificationLevel.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\entity\Application.java +C:\Users\Marwa\Desktop\tutorial\rania\jobmgmnt_merges\src\main\java\fr\atlantique\imt\inf211\jobmngt\config\SecurityConfig.java -- GitLab