Changeset 28b3398


Ignore:
Timestamp:
06/09/24 14:24:03 (9 days ago)
Author:
223021 <daniel.ilievski.2@…>
Branches:
main
Children:
b248810
Parents:
19398ad
Message:

Implemented job application functionality, added job advertisement filtering and replaced text areas with editors

Files:
19 added
27 edited

Legend:

Unmodified
Added
Removed
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/config/SecurityConfiguration.java

    r19398ad r28b3398  
    3030        http.csrf(AbstractHttpConfigurer::disable)
    3131                .authorizeHttpRequests(request -> request
    32                         .requestMatchers("/api/job-advertisements/all","/api/job-advertisements/view/**","/api/recruiter/info/**", "/api/auth/**").permitAll()
     32                        // TO DO: FIX PERMISSIONS
     33                        .requestMatchers("/api/job-advertisements/**","/api/job-advertisements/view/**","/api/recruiter/info/**",
     34                                "/api/job-advertisements/apply/**","/api/auth/**", "/api/resume/**", "/api/my-applications/**", "/api/applications/{id}/update").permitAll()
    3335                        //.requestMatchers("/api/job-advertisements/**").hasAnyAuthority(Role.ROLE_RECRUITER.name())
    3436                        .anyRequest().authenticated())
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/controllers/JobAdvertisementController.java

    r19398ad r28b3398  
    33
    44import lombok.AllArgsConstructor;
     5import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.applications.Application;
     6import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.applications.DTO.ApplicationDTO;
     7import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.job_advertisements.DTO.JobAdFilterDTO;
    58import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.job_advertisements.DTO.JobAdvertisementDTO;
    69import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.job_advertisements.DTO.JobAdDetailsDTO;
    710import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.job_advertisements.JobAdvertisement;
     11import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.service.intef.ApplicationService;
    812import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.service.intef.JobAdvertisementService;
    913import org.springframework.http.HttpStatus;
    1014import org.springframework.http.ResponseEntity;
    1115import org.springframework.web.bind.annotation.*;
     16import org.springframework.web.multipart.MultipartFile;
    1217
    1318import java.util.List;
     
    2025
    2126    private final JobAdvertisementService jobAdvertisementService;
     27    private final ApplicationService applicationService;
    2228
    2329    @PostMapping("/add")
     
    4551    }
    4652
     53    @PostMapping("/recruiter/{id}/filtered")
     54    public ResponseEntity<?> filterJobAdvertisementsByRecruiterId(@PathVariable Long id, @RequestBody JobAdFilterDTO jobAdFilterDTO) {
     55        List<JobAdDetailsDTO> jobAdDetailsDTOS = jobAdvertisementService.filterJobAdvertisementsByRecruiterId(id, jobAdFilterDTO);
     56        return new ResponseEntity<>(jobAdDetailsDTOS, HttpStatus.OK);
     57    }
     58
    4759    @GetMapping("/all")
    4860    public ResponseEntity<?> findAllJobAdvertisements() {
     
    5163    }
    5264
    53     @GetMapping("/view/{id}")
     65    @PostMapping("/filtered")
     66    public ResponseEntity<?> filterJobAdvertisements(@RequestBody JobAdFilterDTO jobAdFilterDTO) {
     67        List<JobAdDetailsDTO> jobAdDetailsDTOS = jobAdvertisementService.filterJobAdvertisements(jobAdFilterDTO);
     68        return new ResponseEntity<>(jobAdDetailsDTOS, HttpStatus.OK);
     69    }
     70
     71    @GetMapping("/{id}")
    5472    public ResponseEntity<?> findJobAdvertisementById(@PathVariable Long id) {
    5573        JobAdDetailsDTO jobAdDetailsDTO = jobAdvertisementService.findJobAdvertisementById(id);
    5674        return new ResponseEntity<>(jobAdDetailsDTO, HttpStatus.OK);
    5775    }
     76
     77
     78
    5879}
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/models/job_advertisements/JobAdvertisement.java

    r19398ad r28b3398  
    88import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.enumerations.JobType;
    99import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.job_advertisements.DTO.JobAdDetailsDTO;
     10import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.Recruiter;
    1011import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.User;
    1112
     
    2021public class JobAdvertisement {
    2122
    22     public JobAdvertisement(User recruiter, String title, String description, String industry, int startingSalary, LocalDate activeUntil, JobType jobType, EmploymentStatus employmentStatus) {
     23    public JobAdvertisement(Recruiter recruiter, String title, String description, String industry, int startingSalary, LocalDate activeUntil, JobType jobType, EmploymentStatus employmentStatus) {
    2324        this.recruiter = recruiter;
    2425        this.title = title;
     
    3839
    3940    @ManyToOne
    40     private User recruiter;
     41    private Recruiter recruiter;
    4142
    4243    private String title;
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/repositories/RecruiterRepository.java

    r19398ad r28b3398  
    55import org.springframework.data.jpa.repository.JpaRepository;
    66
     7import java.util.Optional;
     8
    79public interface RecruiterRepository extends JpaRepository<Recruiter, Long> {
    8     Recruiter findRecruiterByEmail(String email);
     10    Optional<Recruiter> findRecruiterByEmail(String email);
    911}
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/impl/JobAdvertisementServiceImpl.java

    r19398ad r28b3398  
    55import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.enumerations.JobType;
    66import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.job_advertisements.DTO.JobAdDetailsDTO;
     7import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.job_advertisements.DTO.JobAdFilterDTO;
    78import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.job_advertisements.DTO.JobAdvertisementDTO;
    89import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.job_advertisements.JobAdvertisement;
     10import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.Recruiter;
    911import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.users.User;
    1012import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.repositories.JobAdvertisementRepository;
     13import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.repositories.RecruiterRepository;
    1114import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.repositories.UserRepository;
    1215import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.service.intef.JobAdvertisementService;
     
    1518import java.time.LocalDate;
    1619import java.time.LocalDateTime;
     20import java.util.Comparator;
    1721import java.util.List;
    1822
     
    2226    private final UserRepository userRepository;
    2327    private final JobAdvertisementRepository jobAdvertisementRepository;
     28    private final RecruiterRepository recruiterRepository;
    2429
    2530    @Override
    2631    public JobAdDetailsDTO addJobAdvertisement(JobAdvertisementDTO jobAdvertisementDTO) {
    27         User recruiter = userRepository.findByEmail(jobAdvertisementDTO.getEmail()).orElseThrow(() -> new IllegalArgumentException("User not found"));
     32        Recruiter recruiter = recruiterRepository.findRecruiterByEmail(jobAdvertisementDTO.getEmail()).orElseThrow(() -> new IllegalArgumentException("User not found"));
    2833        JobAdvertisement jobAdvertisement = new JobAdvertisement(
    2934                recruiter,
     
    3540                JobType.valueOf(jobAdvertisementDTO.getJobType()),
    3641                EmploymentStatus.valueOf(jobAdvertisementDTO.getEmploymentStatus())
    37                 );
     42        );
    3843        jobAdvertisementRepository.save(jobAdvertisement);
    3944        return JobAdvertisement.mapToJobAdDetailsDTO(jobAdvertisement);
     
    7984
    8085    @Override
     86    public List<JobAdDetailsDTO> filterJobAdvertisements(JobAdFilterDTO jobAdFilterDTO) {
     87        List<JobAdvertisement> filteredJobAds = jobAdvertisementRepository.findAll();
     88        filteredJobAds = filter(filteredJobAds, jobAdFilterDTO);
     89
     90        return filteredJobAds.stream()
     91                .map(JobAdvertisement::mapToJobAdDetailsDTO)
     92                .toList();
     93    }
     94
     95    private List<JobAdvertisement> filter(List<JobAdvertisement> filteredJobAdvertisements, JobAdFilterDTO jobAdFilterDTO) {
     96        return filteredJobAdvertisements.stream()
     97                .filter(jobAd -> jobAd.getTitle().toLowerCase().contains(jobAdFilterDTO.getSearchTerm().toLowerCase()))
     98                .filter(jobAd -> jobAdFilterDTO.getIndustry().equals("all") || jobAd.getIndustry().equals(jobAdFilterDTO.getIndustry()))
     99                .sorted(getComparator(jobAdFilterDTO.getSortOrder()))
     100                .toList();
     101    }
     102
     103    private Comparator<JobAdvertisement> getComparator(String sortOrder) {
     104        return switch (sortOrder) {
     105            case "date_newest" -> Comparator.comparing(JobAdvertisement::getPostedOn).reversed();
     106            case "date_oldest" -> Comparator.comparing(JobAdvertisement::getPostedOn);
     107            case "salary_highest" -> Comparator.comparing(JobAdvertisement::getStartingSalary).reversed();
     108            case "salary_lowest" -> Comparator.comparing(JobAdvertisement::getStartingSalary);
     109            default -> Comparator.comparing(JobAdvertisement::getPostedOn); // Default sorting order
     110        };
     111    }
     112
     113
     114    @Override
    81115    public List<JobAdDetailsDTO> findAllJobAdvertisementsByRecruiterId(Long recruiterId) {
    82116        List<JobAdvertisement> jobAdvertisementList = jobAdvertisementRepository.findAllByRecruiterId(recruiterId);
    83 //        jobAdvertisementList.forEach(jobAdvertisement -> {
    84 //            if (!jobAdvertisement.isJobAdActive() && jobAdvertisement.isActive()) {
    85 //                jobAdvertisement.setActive(false);
    86 //                jobAdvertisementRepository.save(jobAdvertisement);
    87 //            } else if (jobAdvertisement.isJobAdActive() && !jobAdvertisement.isActive()) {
    88 //                jobAdvertisement.setActive(true);
    89 //                jobAdvertisementRepository.save(jobAdvertisement);
    90 //            }
    91 //        });
    92117
    93118        return jobAdvertisementList.stream()
     
    95120                .toList();
    96121    }
     122
     123    @Override
     124    public List<JobAdDetailsDTO> filterJobAdvertisementsByRecruiterId(Long recruiterId, JobAdFilterDTO jobAdFilterDTO) {
     125        List<JobAdvertisement> filteredJobAds = jobAdvertisementRepository.findAllByRecruiterId(recruiterId);
     126        filteredJobAds = filter(filteredJobAds, jobAdFilterDTO);
     127        return filteredJobAds.stream()
     128                .map(JobAdvertisement::mapToJobAdDetailsDTO)
     129                .toList();
     130    }
     131
    97132    @Override
    98133    public JobAdDetailsDTO findJobAdvertisementById(Long id) {
  • jobvista-backend/src/main/java/mk/ukim/finki/predmeti/internettehnologii/jobvistabackend/service/intef/JobAdvertisementService.java

    r19398ad r28b3398  
    22
    33import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.job_advertisements.DTO.JobAdDetailsDTO;
     4import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.job_advertisements.DTO.JobAdFilterDTO;
    45import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.job_advertisements.DTO.JobAdvertisementDTO;
    56import mk.ukim.finki.predmeti.internettehnologii.jobvistabackend.models.job_advertisements.JobAdvertisement;
     
    1415
    1516    List<JobAdDetailsDTO> findAllJobAdvertisements();
     17    List<JobAdDetailsDTO> filterJobAdvertisements(JobAdFilterDTO jobAdFilterDTO);
    1618    List<JobAdDetailsDTO> findAllJobAdvertisementsByRecruiterId(Long recruiterId);
     19    List<JobAdDetailsDTO> filterJobAdvertisementsByRecruiterId(Long recruiterId, JobAdFilterDTO jobAdFilterDTO);
    1720    JobAdDetailsDTO findJobAdvertisementById(Long id);
    1821
  • jobvista-backend/src/main/resources/application.properties

    r19398ad r28b3398  
    66spring.datasource.password=postgres
    77
    8 spring.jpa.hibernate.ddl-auto=create-drop
     8#spring.jpa.hibernate.ddl-auto=create-drop
     9spring.jpa.hibernate.ddl-auto=update
    910spring.jpa.show-sql=true
    1011
    1112spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
     13
     14file.upload-dir=./uploads
     15
     16spring.servlet.multipart.enabled=true
     17spring.servlet.multipart.max-file-size=2MB
     18spring.servlet.multipart.max-request-size=2MB
  • jobvista-frontend/package-lock.json

    r19398ad r28b3398  
    1919        "axios": "^1.6.8",
    2020        "formik": "^2.4.6",
     21        "primereact": "^10.6.6",
     22        "quill": "^2.0.2",
    2123        "react": "^18.3.1",
    2224        "react-dom": "^18.3.1",
     
    89308932      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
    89318933    },
     8934    "node_modules/fast-diff": {
     8935      "version": "1.3.0",
     8936      "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
     8937      "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="
     8938    },
    89328939    "node_modules/fast-glob": {
    89338940      "version": "3.3.2",
     
    1308813095      "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
    1308913096    },
     13097    "node_modules/lodash.clonedeep": {
     13098      "version": "4.5.0",
     13099      "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
     13100      "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
     13101    },
    1309013102    "node_modules/lodash.debounce": {
    1309113103      "version": "4.0.8",
    1309213104      "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
    1309313105      "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
     13106    },
     13107    "node_modules/lodash.isequal": {
     13108      "version": "4.5.0",
     13109      "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
     13110      "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="
    1309413111    },
    1309513112    "node_modules/lodash.memoize": {
     
    1384813865        "tslib": "^2.0.3"
    1384913866      }
     13867    },
     13868    "node_modules/parchment": {
     13869      "version": "3.0.0",
     13870      "resolved": "https://registry.npmjs.org/parchment/-/parchment-3.0.0.tgz",
     13871      "integrity": "sha512-HUrJFQ/StvgmXRcQ1ftY6VEZUq3jA2t9ncFN4F84J/vN0/FPpQF+8FKXb3l6fLces6q0uOHj6NJn+2xvZnxO6A=="
    1385013872    },
    1385113873    "node_modules/parent-module": {
     
    1532415346      }
    1532515347    },
     15348    "node_modules/primereact": {
     15349      "version": "10.6.6",
     15350      "resolved": "https://registry.npmjs.org/primereact/-/primereact-10.6.6.tgz",
     15351      "integrity": "sha512-+C0Bt6vS/jh09DQVS4UXpVctbvqJDUC3t3mVdGmhmIINYD8kdfL3fvc3bUGniGxkKKzwkSYdAQXhZlcgj8LUgw==",
     15352      "dependencies": {
     15353        "@types/react-transition-group": "^4.4.1",
     15354        "react-transition-group": "^4.4.1"
     15355      },
     15356      "engines": {
     15357        "node": ">=14.0.0"
     15358      },
     15359      "peerDependencies": {
     15360        "@types/react": "^17.0.0 || ^18.0.0",
     15361        "react": "^17.0.0 || ^18.0.0",
     15362        "react-dom": "^17.0.0 || ^18.0.0"
     15363      },
     15364      "peerDependenciesMeta": {
     15365        "@types/react": {
     15366          "optional": true
     15367        }
     15368      }
     15369    },
    1532615370    "node_modules/process-nextick-args": {
    1532715371      "version": "2.0.1",
     
    1545315497        }
    1545415498      ]
     15499    },
     15500    "node_modules/quill": {
     15501      "version": "2.0.2",
     15502      "resolved": "https://registry.npmjs.org/quill/-/quill-2.0.2.tgz",
     15503      "integrity": "sha512-QfazNrhMakEdRG57IoYFwffUIr04LWJxbS/ZkidRFXYCQt63c1gK6Z7IHUXMx/Vh25WgPBU42oBaNzQ0K1R/xw==",
     15504      "dependencies": {
     15505        "eventemitter3": "^5.0.1",
     15506        "lodash-es": "^4.17.21",
     15507        "parchment": "^3.0.0",
     15508        "quill-delta": "^5.1.0"
     15509      },
     15510      "engines": {
     15511        "npm": ">=8.2.3"
     15512      }
     15513    },
     15514    "node_modules/quill-delta": {
     15515      "version": "5.1.0",
     15516      "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-5.1.0.tgz",
     15517      "integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==",
     15518      "dependencies": {
     15519        "fast-diff": "^1.3.0",
     15520        "lodash.clonedeep": "^4.5.0",
     15521        "lodash.isequal": "^4.5.0"
     15522      },
     15523      "engines": {
     15524        "node": ">= 12.0.0"
     15525      }
     15526    },
     15527    "node_modules/quill/node_modules/eventemitter3": {
     15528      "version": "5.0.1",
     15529      "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
     15530      "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="
    1545515531    },
    1545615532    "node_modules/raf": {
  • jobvista-frontend/package.json

    r19398ad r28b3398  
    1414    "axios": "^1.6.8",
    1515    "formik": "^2.4.6",
     16    "primereact": "^10.6.6",
     17    "quill": "^2.0.2",
    1618    "react": "^18.3.1",
    1719    "react-dom": "^18.3.1",
  • jobvista-frontend/src/App.css

    r19398ad r28b3398  
    44.App {
    55  background-color: rgb(243, 242, 241);
     6  //background-color: #EBF2FC;
    67  height: 100vh;
    78  overflow-y: auto;
     
    4041
    4142.react-responsive-modal-overlay {
    42   backdrop-filter: blur(2px);
     43  backdrop-filter: blur(3px);
    4344}
    4445
     
    4748.col {
    4849  height: 280px !important;
     50}
     51
     52.add-new-card {
     53  border-radius: 8px;
     54  background-color: transparent;
     55  border: 3px dashed grey;
     56  transition: all 0.3s ease;
     57  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
     58  transform: translate(0, 0);
     59  height: 260px;
     60  width: 100%;
     61  color: grey;
     62  font-family: Ubuntu;
     63  text-transform: uppercase;
     64}
     65.add-new-card h3 {
     66  font-size: 25px;
     67}
     68.add-new-card:hover {
     69  /*transform: translate(0, 8px);*/
     70  /*box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);*/
     71  color: black;
     72  border: 3px dashed black;
    4973}
    5074
  • jobvista-frontend/src/auth/RoutesConfig.js

    r19398ad r28b3398  
    77import {JobAdvertisements} from "../views/job_advertisements/JobAdvertisements";
    88import {JobAdDetails} from "../views/job_advertisements/JobAdDetails";
     9import {ApplicationsByJobAd} from "../views/applications/ApplicationsByJobAd";
     10import {ApplicationsByJobSeeker} from "../views/applications/ApplicationsByJobSeeker";
    911export const RoutesConfig = () => {
    1012
     
    1618                <Route path="/signup/job-seeker" element={<SignUpJobSeekerForm/>}></Route>
    1719                <Route path="/my-job-advertisements" element={<JobAdvertisements/>}></Route>
    18                 <Route path="/job-advertisements/view/:id" element={<JobAdDetails/>}></Route>
    19                 <Route path="/my-job-advertisements/view/:id" element={<JobAdDetails/>}></Route>
     20                <Route path="/my-applications" element={<ApplicationsByJobSeeker/>}></Route>
     21                <Route path="/job-advertisements/:id" element={<JobAdDetails/>}></Route>
     22                <Route path="/my-job-advertisements/:advertisement_id/applications" element={<ApplicationsByJobAd/>}></Route>
    2023            </Routes>
    2124    )
  • jobvista-frontend/src/redux/actionTypes.js

    r19398ad r28b3398  
    99export const DELETE_JOB_ADVERTISEMENT = "DELETE_JOB_ADVERTISEMENT"
    1010export const FETCH_JOB_ADVERTISEMENTS = "FETCH_JOB_ADVERTISEMENTS"
     11export const FILTER_JOB_ADVERTISEMENTS = "FILTER_JOB_ADVERTISEMENTS"
    1112export const FETCH_JOB_ADVERTISEMENTS_BY_RECRUITER = "FETCH_JOB_ADVERTISEMENTS_BY_RECRUITER"
     13export const FILTER_JOB_ADVERTISEMENTS_BY_RECRUITER = "FILTER_JOB_ADVERTISEMENTS_BY_RECRUITER"
     14export const SUBMIT_APPLICATION = "SUBMIT_APPLICATION"
     15export const UPDATE_APPLICATION_STATUS = "UPDATE_APPLICATION_STATUS"
     16export const FETCH_APPLICATIONS_BY_JOB_ID = "FETCH_APPLICATIONS_BY_JOB_ID"
     17export const FETCH_APPLICATIONS_BY_JOB_SEEKER_ID = "FETCH_APPLICATIONS_BY_JOB_SEEKER_ID"
     18export const DOWNLOAD_RESUME = "DOWNLOAD_RESUME"
     19
     20
  • jobvista-frontend/src/redux/actions/jobAdvertisementActions.js

    r19398ad r28b3398  
    44    CURRENT_USER, DELETE_JOB_ADVERTISEMENT, EDIT_JOB_ADVERTISEMENT,
    55    FETCH_JOB_ADVERTISEMENTS,
    6     FETCH_JOB_ADVERTISEMENTS_BY_RECRUITER
     6    FETCH_JOB_ADVERTISEMENTS_BY_RECRUITER, FILTER_JOB_ADVERTISEMENTS
    77} from "../actionTypes";
    88
     
    1010    addJobAdvertisement: (jobAdvertisement, callback) => {
    1111        return dispatch => {
    12             axios.post("/job-advertisements/add", jobAdvertisement)
     12            axios.post("/job-advertisements/add",  jobAdvertisement, {
     13                headers: {
     14                    'Content-Type': 'application/json'
     15                },
     16            })
    1317                .then(response => {
    1418                    dispatch({
     
    5256    },
    5357    fetchJobAdvertisementById: (id, callback) => {
    54         axios.get("/job-advertisements/view/" + id)
     58        axios.get("/job-advertisements/" + id)
    5559            .then(response => {
    5660                callback(true, response)
    5761            }).catch(error => {
    58                 callback(false, error)
    59             })
     62            callback(false, error)
     63        })
    6064    },
    6165
     
    7478        }
    7579    },
     80    filterJobAdvertisements: (filter, callback) => {
     81        axios.post("/job-advertisements/filtered", filter)
     82            .then(response => {
     83                callback(true, response)
     84            }).catch((error) => {
     85            callback(false, error)        })
    7686
    77     fetchJobAdvertisementsByRecruiter: (id, callback) => {
     87    },
     88
     89    fetchJobAdvertisementsByRecruiter: (callback) => {
    7890        return dispatch => {
    7991            let currentUser = JSON.parse(localStorage.getItem(CURRENT_USER));
     
    8698                    callback(true, response)
    8799                }).catch((error) => {
    88                 console.log("ERROR")
    89100                callback(false, error)
    90101            })
    91102        }
     103    },
     104
     105    filterJobAdvertisementsByRecruiter: (filter, callback) => {
     106
     107        let currentUser = JSON.parse(localStorage.getItem(CURRENT_USER));
     108        axios.post("/job-advertisements/recruiter/" + currentUser.id + "/filtered", filter)
     109            .then(response => {
     110                callback(true, response)
     111            }).catch((error) => {
     112            callback(false, error)
     113        })
     114
    92115    },
    93116
  • jobvista-frontend/src/redux/reducers/jobAdvertisementReducer.js

    r19398ad r28b3398  
    33    CURRENT_USER, DELETE_JOB_ADVERTISEMENT, EDIT_JOB_ADVERTISEMENT,
    44    FETCH_JOB_ADVERTISEMENTS,
    5     FETCH_JOB_ADVERTISEMENTS_BY_RECRUITER
     5    FETCH_JOB_ADVERTISEMENTS_BY_RECRUITER, FILTER_JOB_ADVERTISEMENTS, FILTER_JOB_ADVERTISEMENTS_BY_RECRUITER
    66} from "../actionTypes";
    77import {sortElementsByDateCreated} from "../../utils/utils";
     
    4848            }
    4949
     50
    5051        case FETCH_JOB_ADVERTISEMENTS_BY_RECRUITER:
    5152            return {
  • jobvista-frontend/src/redux/store.js

    r19398ad r28b3398  
    33import authReducer from "./reducers/authReducer";
    44import jobAdReducer from "./reducers/jobAdvertisementReducer";
     5import applicationReducer from "./reducers/applicationReducer"
    56
    67// const rootReducer = combineReducers({
     
    1617    reducer: {
    1718        auth: authReducer,
    18         jobAd: jobAdReducer
     19        jobAd: jobAdReducer,
     20        appl: applicationReducer
    1921    },
    2022});
  • jobvista-frontend/src/utils/utils.js

    r19398ad r28b3398  
    33export const sortElementsByDateCreated = (array) => {
    44    return array.slice().sort((a, b) => {
    5         return new Date(b.postedAt).getTime() - new Date(a.postedAt).getTime()
     5        return new Date(b.postedOn).getTime() - new Date(a.postedOn).getTime()
     6    });
     7}
     8
     9export const sortElementsBySubmissionDate = (array) => {
     10    return array.slice().sort((a, b) => {
     11        return new Date(b.postedOn).getTime() - new Date(a.postedOn).getTime()
    612    });
    713}
  • jobvista-frontend/src/views/dashboard/Dashboard.css

    r19398ad r28b3398  
    1111}
    1212
    13 
    14 .head-dashboard-box .head-component {
    15     display: inline;
     13.head-dashboard-box .filter-container {
     14    display: inline-flex;
     15    justify-content: center;
     16    gap: 10px;
    1617}
    1718
     
    1920.head-dashboard-box .search-container {
    2021    position: relative;
    21     float: left;
     22    /*float: left;*/
     23    display: inline-block;
    2224}
    2325.head-dashboard-box .search-container .search-input {
     
    2628    padding: 5px 30px;
    2729    border-radius: 10px;
    28     margin-right: 15px;
     30    /*margin-right: 15px;*/
    2931}
    3032
    31 .search-container .search-input:focus {
     33.head-dashboard-box .search-container .search-input:focus {
    3234    outline-color: #6367ef;
    3335}
    3436
    35 .search-container i {
     37.head-dashboard-box .search-container i {
    3638    left: 10px;
    3739    position: absolute;
     
    3941}
    4042
    41 .item {
    42     width: 35%;
     43.head-dashboard-box .item {
     44    width: 20%;
    4345    display: inline-block;
    44     margin-left: 10px;
    45     float: right;
     46    /*margin-left: 10px;*/
     47    /*float: left !important;*/
    4648}
  • jobvista-frontend/src/views/dashboard/Dashboard.js

    r19398ad r28b3398  
    2020
    2121    const [role, setRole] = useState("");
    22     const [sortOrder, setSortOrder] = useState("newest");
    23     const [selectedDateRange, setSelectedDateRange] = useState("all");
     22    const [selectedSortOrder, setSelectedSortOrder] = useState("date_newest");
     23    const [selectedIndustry, setSelectedIndustry] = useState("all");
    2424    const [searchTerm, setSearchTerm] = useState("");
    2525    const [dispatched, setDispatched] = useState(false)
     
    3232
    3333    useEffect(() => {
    34         if(!dispatched && jobAdvertisementsState.length == 0) {
     34        if (!dispatched && jobAdvertisementsState.length == 0) {
    3535            dispatch(JobAdvertisementActions.fetchJobAdvertisements((success, response) => {
    3636                if (success && response.data.length > 0) {
     
    3939                setDispatched(true)
    4040                console.log("Fetch all job advertisements GET")
    41                 console.log(response.data)
    4241            }))
    4342
     
    4645            console.log("Fetch all job advertisements STATE")
    4746        }
    48     }, [])
     47    }, [jobAdvertisementsState])
    4948
     49    let filterJobAdvertisements = () => {
     50        JobAdvertisementActions.filterJobAdvertisements(
     51            {
     52                searchTerm: searchTerm,
     53                industry: selectedIndustry,
     54                sortOrder: selectedSortOrder
     55            }, (success, response) => {
     56                if (success) {
     57                    setJobAdvertisements(response.data);
     58                }
     59            }
     60        )
     61    }
    5062
    5163    return (
     
    5365            <div className="head-dashboard-box">
    5466                <div className="row">
    55                     <div className="col-md-3">
    56                         <div className="search-container head-component">
    57                             <i className="fa-solid fa-magnifying-glass blue-colored"></i>
     67                    <div className="col-md-12 filter-container">
     68                        <div className="search-container">
     69                            <i className="fa-solid fa-magnifying-glass search-icon"></i>
    5870                            <input
    5971                                className="search-input"
    6072                                type="text"
    6173                                placeholder="Search job advertisement by title..."
    62                                 //value={searchTerm}
    63                                 //onChange={event => setSearchTerm(event.target.value)}
     74                                value={searchTerm}
     75                                onChange={event => setSearchTerm(event.target.value)}
    6476                            />
    6577                        </div>
    66                     </div>
    67                     <div className="col-md-9">
    6878                        <div className="sort-section item">
    6979                            <Select
    7080                                defaultValue={{value: "all", label: "All industries"}}
    71                                 //value={sortOrder.value}
    72                                 //onChange ={option => setSortOrder(option.value)}
     81                                value={selectedIndustry.value}
     82                                onChange={option => setSelectedIndustry(option.value)}
    7383                                options={industryOptionsFilter}
    7484                                className="sort-range sort"
     
    7888                            <Select
    7989                                defaultValue={{value: "newest", label: "Date (Newest First)"}}
    80                                 //value={sortOrder.value}
    81                                 //onChange ={option => setSortOrder(option.value)}
    82                                 options = {sortOptions}
     90                                value={selectedSortOrder.value}
     91                                onChange={option => setSelectedSortOrder(option.value)}
     92                                options={sortOptions}
    8393                                className="sort-range sort"
    8494                            />
    8595                        </div>
    86                         <div className="date-range-section item">
    87                             <Select
    88                                 defaultValue={{value: "all", label: "Lifetime"}}
    89                                 //value={selectedDateRange.value}
    90                                 //onChange={option => setSelectedDateRange(option.value)}
    91                                 options={dataRangeOptions}
    92                                 className="date-range sort"
    93                             />
    94                         </div>
     96                        <button onClick={filterJobAdvertisements} className="btn-open-modal">Find jobs</button>
    9597                    </div>
     98                    {/*<div className="col-md-3">*/}
     99
     100                    {/*<div className="date-range-section item">*/}
     101                    {/*    <Select*/}
     102                    {/*        defaultValue={{value: "all", label: "Lifetime"}}*/}
     103                    {/*        //value={selectedDateRange.value}*/}
     104                    {/*        //onChange={option => setSelectedDateRange(option.value)}*/}
     105                    {/*        options={dataRangeOptions}*/}
     106                    {/*        className="date-range sort"*/}
     107                    {/*    />*/}
     108                    {/*</div>*/}
     109                    {/*</div>*/}
    96110                </div>
    97111            </div>
     
    104118                                <div className="card-head">
    105119                                    <span className="hourly-salary"><b>${jobAd.startingSalary}/hr</b></span>
    106                                     <span className="job-type"> {jobAd.jobType===JobType.JOB ? "Job" : "Internship"}</span>
     120                                    <span
     121                                        className="job-type"> {jobAd.jobType === JobType.JOB ? "Job" : "Internship"}</span>
    107122                                    {!jobAd.active && <span className="expired">Expired</span>}
    108123                                </div>
    109124                                <div className="card-body">
    110125                                    <h5 className="card-title">{jobAd.title}</h5>
    111                                     <span>{jobAd.industry} • <span style={{color: "black", fontWeight: "bold"}}>{formatRelativeTime(jobAd.postedOn)}</span></span>
     126                                    <span>{jobAd.industry} • <span style={{
     127                                        color: "black",
     128                                        fontWeight: "bold"
     129                                    }}>{formatRelativeTime(jobAd.postedOn)}</span></span>
    112130                                    <div className="card-info">
    113                                         <span><i className="fa-solid fa-building" style={{color: "#000000"}}></i> Company: <span style={{color: "black", fontWeight: "bold"}}>{jobAd.recruiterName}</span></span> <br/>
     131                                        <span><i className="fa-solid fa-building"
     132                                                 style={{color: "#000000"}}></i> Company: <span style={{
     133                                            color: "black",
     134                                            fontWeight: "bold"
     135                                        }}>{jobAd.recruiterName}</span></span> <br/>
    114136                                    </div>
    115137
    116138                                    <div className="aligned">
    117                                         <Link to={`/job-advertisements/view/${jobAd.id}`} className="card-button">Read more</Link>
    118                                         {role===Roles.JOBSEEKER &&
    119                                             <>
    120                                                 {jobAd.active && <button className="card-button">Apply now</button> }
    121                                                 {!jobAd.active && <button className="card-button disabled">Apply now</button> }
    122                                             </>
    123                                         }
     139                                        <Link to={`/job-advertisements/${jobAd.id}`} className="card-button">Read
     140                                            more</Link>
    124141                                    </div>
    125142
  • jobvista-frontend/src/views/job_advertisements/AddJobAdModal.js

    r19398ad r28b3398  
    11import React, {useState} from "react";
    22import "./Form.css";
     3
     4import { Editor } from 'primereact/editor';
    35
    46import 'react-responsive-modal/styles.css';
     
    1921export const AddJobAdModal = () => {
    2022    const [modal, setModal] = useState(false);
     23    const [text, setText] = useState("");
    2124    const dispatch = useDispatch();
    2225    const auth = useSelector(state => state.auth.currentUser)
     
    5255                    employmentStatus: values.employmentStatus.value,
    5356                }, (success, response) => {
    54                     if(success) {
     57                    if (success) {
    5558                        console.log("Job Advertisement added")
    5659                        toggleModal()
     
    6669
    6770    return (<div className="modal-wrap">
    68             <button onClick={toggleModal} className="btn-open-modal">POST ADVERTISEMENT</button>
    69             <Modal open={modal} onClose={toggleModal} center classNames="job-advertisement-modal">
    70                 <div className="head-modal">
    71                     <h3>Post Job Advertisement</h3>
    72                     <i className="fa-solid fa-x btn-close-modal" onClick={toggleModal}></i>
    73                 </div>
     71        <div className="col">
     72            <button onClick={toggleModal} className="add-new-card">
     73                <h3>+ Post Advertisement</h3>
     74            </button>
     75        </div>
     76        {/*<button onClick={toggleModal} className="btn-open-modal">POST ADVERTISEMENT</button>*/}
     77        <Modal open={modal} onClose={toggleModal} center classNames="job-advertisement-modal">
     78            <div className="head-modal">
     79                <h3>Post Job Advertisement</h3>
     80                <i className="fa-solid fa-x btn-close-modal" onClick={toggleModal}></i>
     81            </div>
    7482
    75                 <div className="modal-content">
    76                     <form onSubmit={handleSubmit(addJobAdvertisement)}>
    77                         <div className="row">
    78                             <div className="col-md-7">
     83            <div className="modal-content">
     84                <form onSubmit={handleSubmit(addJobAdvertisement)}>
     85                    <div className="row">
     86                        <div className="col-md-7">
    7987
    80                                 <label className="label">Job title:</label>
    81                                 <input type="text" {...register("title")}/>
    82                                 <p style={{color: "red"}}>{errors.title?.message}</p>
     88                            <label className="label">Job title:</label>
     89                            <input type="text" {...register("title")}/>
     90                            <p style={{color: "red"}}>{errors.title?.message}</p>
    8391
    84                                 <label className="label">Job description:</label>
    85                                 <textarea type="text" placeholder="Describe the job position and all the requirements"
    86                                           className="description-textarea" {...register("description")}/>
    87                                 <p style={{color: "red"}}>{errors.description?.message}</p>
    88                             </div>
    89 
    90                             <div className="col-md-5">
    91                                 <label className="label">Hourly rate:</label>
    92                                 <input {...register("startingSalary")}/>
    93                                 <p style={{color: "red"}}>{errors.startingSalary?.message}</p>
    94 
    95                                 <label className="label">Industry:</label>
    96                                 <Controller
    97                                     name="industry"
    98                                     control={control}
    99                                     render={({ field })  => (<Select
    100                                             {...field}
    101                                             options={industryOptions}
    102                                         />)}
    103                                 />
    104                                 <p style={{color: "red"}}>{errors.industry?.message}</p>
    105 
    106                                 <label className="label">Job type:</label>
    107                                 <Controller
    108                                     name="jobType"
    109                                     control={control}
    110                                     render={({ field }) => (<Select
    111                                             {...field}
    112                                             options={jobTypeOptions}
    113                                         />)}
    114                                 />
    115                                 <p style={{color: "red"}}>{errors.jobType?.message}</p>
    116 
    117                                 <label className="label">Employment status</label>
    118                                 <Controller
    119                                     name="employmentStatus"
    120                                     control={control}
    121                                     render={({ field }) => (<Select
    122                                             {...field}
    123                                             options={employmentStatusOptions}
    124                                         />)}
    125                                 />
    126                                 <p style={{color: "red"}}>{errors.employmentStatus?.message}</p>
    127 
    128                                 <label htmlFor="start">Active until:</label>
    129                                 <input type="date" defaultValue={minimumDate.toLocaleDateString('en-CA')}
    130                                        min={minimumDate.toLocaleDateString('en-CA')} onChange={(event) => console.log(event.target.value)}
    131                                        {...register("date")}/>
     92                            <label className="label">Job description:</label>
     93                            {/*<textarea type="text" placeholder="Describe the job position and all the requirements"*/}
     94                            {/*          className="description-textarea" {...register("description")}/>*/}
    13295
    13396
    134                             </div>
     97                            <Controller
     98                                name="description"
     99                                control={control}
     100                                render={({ field }) => (
     101                                    <Editor
     102                                        value={field.value}
     103                                        onTextChange={(e) => field.onChange(e.htmlValue)}
     104                                        style={{ height: '300px', fontSize: "16px", fontFamily: "Segoe UI" }}
     105                                    />
     106                                )}
     107                            />
     108                            <p style={{color: "red"}}>{errors.description?.message}</p>
    135109                        </div>
    136110
    137                         <div className="aligned">
    138                             <button className="submit-btn"> Submit</button>
     111                        <div className="col-md-5">
     112                            <label className="label">Hourly rate:</label>
     113                            <input {...register("startingSalary")}/>
     114                            <p style={{color: "red"}}>{errors.startingSalary?.message}</p>
     115
     116                            <label className="label">Industry:</label>
     117                            <Controller
     118                                name="industry"
     119                                control={control}
     120                                render={({field}) => (<Select
     121                                    {...field}
     122                                    options={industryOptions}
     123                                />)}
     124                            />
     125                            <p style={{color: "red"}}>{errors.industry?.message}</p>
     126
     127                            <label className="label">Job type:</label>
     128                            <Controller
     129                                name="jobType"
     130                                control={control}
     131                                render={({field}) => (<Select
     132                                    {...field}
     133                                    options={jobTypeOptions}
     134                                />)}
     135                            />
     136                            <p style={{color: "red"}}>{errors.jobType?.message}</p>
     137
     138                            <label className="label">Employment status</label>
     139                            <Controller
     140                                name="employmentStatus"
     141                                control={control}
     142                                render={({field}) => (<Select
     143                                    {...field}
     144                                    options={employmentStatusOptions}
     145                                />)}
     146                            />
     147                            <p style={{color: "red"}}>{errors.employmentStatus?.message}</p>
     148
     149                            <label htmlFor="start">Active until:</label>
     150                            <input type="date" defaultValue={minimumDate.toLocaleDateString('en-CA')}
     151                                   min={minimumDate.toLocaleDateString('en-CA')}
     152                                   onChange={(event) => console.log(event.target.value)}
     153                                   {...register("date")}/>
     154
     155
    139156                        </div>
     157                    </div>
    140158
    141                     </form>
    142                 </div>
    143             </Modal>
    144         </div>)
     159                    <div className="aligned">
     160                        <button className="submit-btn"> Submit</button>
     161                    </div>
     162
     163                </form>
     164            </div>
     165        </Modal>
     166    </div>)
    145167}
  • jobvista-frontend/src/views/job_advertisements/EditJobAdModal.js

    r19398ad r28b3398  
    1515import {useDispatch, useSelector} from "react-redux";
    1616import {JobAdvertisementActions} from "../../redux/actions/jobAdvertisementActions";
     17import {Editor} from "primereact/editor";
    1718
    1819
     
    8586
    8687                            <label className="label">Job description:</label>
    87                             <textarea type="text" defaultValue={jobAd.props.description} placeholder="Describe the job position and all the requirements"
    88                                       className="description-textarea" {...register("description")}/>
     88                            {/*<textarea type="text" defaultValue={jobAd.props.description} placeholder="Describe the job position and all the requirements"*/}
     89                            {/*          className="description-textarea" {...register("description")}/>*/}
     90
     91                            <Controller
     92                                name="description"
     93                                control={control}
     94                                render={({ field }) => (
     95                                    <Editor
     96                                        defaultValue={jobAd.props.description}
     97                                        value={jobAd.props.description}
     98                                        onTextChange={(e) => field.onChange(e.htmlValue)}
     99                                        style={{ height: '300px', fontSize: "16px", fontFamily: "Segoe UI" }}
     100                                    />
     101                                )}
     102                            />
     103
    89104                            <p style={{color: "red"}}>{errors.description?.message}</p>
    90105                        </div>
  • jobvista-frontend/src/views/job_advertisements/Form.css

    r19398ad r28b3398  
    1212    font-weight: 600;
    1313    float: right;
    14     margin-left: 10px;
     14    /*margin-left: 10px;*/
    1515}
    1616
     
    8787.description-textarea {
    8888    height: 285px;
     89}
    8990
     91.applictaion-textarea {
     92    height: 100px;
     93}
     94
     95.resume-link {
     96    display: block;
     97    padding: 5px 10px;
     98    width: 100%;
     99    border-radius: 3px;
     100    border: 1px solid lightgrey;
     101}
     102.custom {
     103    margin-top: 10px;
    90104}
    91105
     
    161175}
    162176
     177.ql-snow .ql-picker.ql-font {
     178    display: none;
     179}
     180.ql-toolbar.ql-snow .ql-picker-options {
     181    background-color: white;
     182}
     183.ql-snow .ql-tooltip {
     184    transform: translateX(100px) !important;
     185}
    163186
    164187
    165188
    166189
     190
  • jobvista-frontend/src/views/job_advertisements/JobAdDetails.js

    r19398ad r28b3398  
    99import {formatRelativeTime} from "../../utils/utils";
    1010import {AddJobAdModal} from "./AddJobAdModal";
     11import {ApplyToJobAdModal} from "../applications/ApplyToJobAdModal";
    1112
    1213
     
    5253
    5354                    <p><i className="fa-solid fa-money-check-dollar"></i> <span>Hourly rate: ${jobAd.startingSalary}</span></p>
    54                     <p><i className="fa-solid fa-briefcase"></i> Employment status: {jobAd.employmentStatus===EmploymentStatus.FULL_TIME ? "Full-time" : "Part-time"}</p>
     55                    <p><i className="fa-solid fa-briefcase"></i> Employment status: {jobAd.employmentStatus==="FULL_TIME" ? "Full-time" : "Part-time"}</p>
    5556                    <p><i className="fa-solid fa-calendar-days"></i> Active until: {new Date(jobAd.activeUntil).toLocaleString('default', { day: 'numeric', month: 'long',  year: 'numeric' })}</p>
    5657
     
    5960                        <p dangerouslySetInnerHTML={{ __html: jobAd.description.replace(/\n/g, "<br>") }}></p>
    6061                    )}
    61 
    62 
    63                     {role===Roles.JOBSEEKER &&
    64                         <>
    65                             {jobAd.active && <button className="card-button apply">Apply now</button> }
    66                             {!jobAd.active && <button className="card-button apply disabled">Apply now</button> }
    67                         </>
    68                     }
     62                    <ApplyToJobAdModal jobAd={jobAd} role={role}/>
    6963
    7064                </div>
     
    7670                    {/*TO DO - AFTER IMPLEMENTING FORM FOR UPDATING PERSONAL INFO*/}
    7771                    <h4>About the company</h4>
    78                     <p>As a pioneering Swiss software company, we provide innovative IT products and tailored digital solutions. We bring decades of experience in designing, developing, and implementing highly scalable, secure, and user-centric software.
     72                    <p>
     73                        For over two decades, we have been harnessing technology to drive meaningful change.  Working side by side with leading brands, we build strategies, products and solutions tailored to unique needs –regardless of industry, region or scale. By combining world-class engineering, industry expertise and a people-centric mindset, we consult and partner with our customers to create technological solutions that drive innovation and transform businesses.
    7974                        <br/><br/>
    80                         Working across the banking, payment, mobility, health, and publishing industries, we are experts at delivering seamless and secure user journeys within these privacy-driven environments. Our business-critical applications are designed to overcome complexity and drive growth.
    81                         <br/><br/>
    82                         We are based in Zurich, Switzerland, and have offices elsewhere in Europe, Asia, and the Middle East. Founded in 1996, we are a business of 800 experts, enabling our clients to create value with trusted software..
     75                        From ideation to production, we support our customers with bespoke solutions across various industries, including payments, insurance, finance and banking, technology, media and entertainment, telecommunications, retail and consumer goods, supply chain and logistics, healthcare and life sciences, energy and resources, government, automotive and travel.
     76
    8377                    </p>
    8478                    <p><span><i className="fa-solid fa-envelope"></i> {recruiterDetails.email}</span> • <span>
  • jobvista-frontend/src/views/job_advertisements/JobAdvertisements.css

    r19398ad r28b3398  
    1 .head-job-advertisements-box {
    2     height: 10%;
    3     width: 100%;
    4     background-color: #fff;
    5     border-radius: 12px;
    6     padding: 15px 20px;
    7     margin-bottom: 20px;
    8     margin-top: 30px;
    9     height: auto;
    10 }
    11 
    12 
    13 .head-job-advertisements-box .head-component {
    14     display: inline;
    15 }
    16 
    17 
    18 .head-job-advertisements-box .search-container {
    19     position: relative;
    20     float: left;
    21 }
    22 .head-job-advertisements-box .search-container .search-input {
    23     width: 400px;
    24     display: inline;
    25     padding: 5px 30px;
    26     border-radius: 10px;
    27     margin-right: 15px;
    28 }
    29 
    30 .search-container .search-input:focus {
    31     outline-color: #6367ef;
    32 }
    33 
    34 .search-container i {
    35     left: 10px;
    36     position: absolute;
    37     top: 10px;
    38 }
    39 
    40 .item {
    41     width: 25%;
    42     display: inline-block;
    43     margin-left: 10px;
    44     float: right;
    45 }
  • jobvista-frontend/src/views/job_advertisements/JobAdvertisements.js

    r19398ad r28b3398  
    66import {JobAdvertisementActions} from "../../redux/actions/jobAdvertisementActions";
    77import {formatRelativeTime, sortElementsByDateCreated} from "../../utils/utils";
    8 import {dataRangeOptions, industryOptions, sortOptions} from "../selectOptions";
     8import {dataRangeOptions, industryOptions, industryOptionsFilter, sortOptions} from "../selectOptions";
    99import Select from "react-select";
    1010import {DeleteJobAdModal} from "./DeleteJobAdModal";
     
    1313import JobType from "../../enumerations/JobType";
    1414
    15 export const JobAdvertisements = () => {
     15export const JobAdvertisements = (props) => {
    1616
    1717    const dispatch = useDispatch();
     
    2121
    2222    const [role, setRole] = useState("");
    23     const [sortOrder, setSortOrder] = useState("newest");
    24     const [selectedDateRange, setSelectedDateRange] = useState("all");
     23    const [selectedSortOrder, setSelectedSortOrder] = useState("date_newest");
     24    const [selectedIndustry, setSelectedIndustry] = useState("all");
    2525    const [searchTerm, setSearchTerm] = useState("");
    2626    const [dispatched, setDispatched] = useState(false)
     
    3535    useEffect(() => {
    3636        if (!dispatched && jobAdvertisementsByRecruiterState.length === 0) {
    37             dispatch(JobAdvertisementActions.fetchJobAdvertisementsByRecruiter("deleteThis", (success, response) => {
     37            dispatch(JobAdvertisementActions.fetchJobAdvertisementsByRecruiter((success, response) => {
    3838                if (success && response.data.length > 0) {
    3939                    setJobAdvertisementsByRecruiter(sortElementsByDateCreated(response.data))
     
    4949    }, [jobAdvertisementsByRecruiterState])
    5050
     51    let filterJobAdvertisements = () => {
     52        JobAdvertisementActions.filterJobAdvertisementsByRecruiter(
     53            {
     54                searchTerm: searchTerm,
     55                industry: selectedIndustry,
     56                sortOrder: selectedSortOrder
     57            }, (success, response) => {
     58                if (success) {
     59                    setJobAdvertisementsByRecruiter(response.data);
     60                }
     61            }
     62        )
     63    }
     64
    5165    return (
    5266        <div className="container">
    53             <div className="head-job-advertisements-box">
     67            <div className="head-dashboard-box">
    5468                <div className="row">
    55                     <div className="col-md-3">
    56                         <div className="search-container head-component">
    57                             <i className="fa-solid fa-magnifying-glass blue-colored"></i>
     69                    <div className="col-md-12 filter-container">
     70                        <div className="search-container">
     71                            <i className="fa-solid fa-magnifying-glass search-icon"></i>
    5872                            <input
    5973                                className="search-input"
    6074                                type="text"
    6175                                placeholder="Search job advertisement by title..."
    62                                 //value={searchTerm}
    63                                 //onChange={event => setSearchTerm(event.target.value)}
     76                                value={searchTerm}
     77                                onChange={event => setSearchTerm(event.target.value)}
    6478                            />
    6579                        </div>
    66                     </div>
    67                     <div className="col-md-9">
    68 
    69                         <AddJobAdModal/>
     80                        <div className="sort-section item">
     81                            <Select
     82                                defaultValue={{value: "all", label: "All industries"}}
     83                                value={selectedIndustry.value}
     84                                onChange={option => setSelectedIndustry(option.value)}
     85                                options={industryOptionsFilter}
     86                                className="sort-range sort"
     87                            />
     88                        </div>
    7089                        <div className="sort-section item">
    7190                            <Select
    7291                                defaultValue={{value: "newest", label: "Date (Newest First)"}}
    73                                 //value={sortOrder.value}
    74                                 //onChange ={option => setSortOrder(option.value)}
     92                                value={selectedSortOrder.value}
     93                                onChange={option => setSelectedSortOrder(option.value)}
    7594                                options={sortOptions}
    7695                                className="sort-range sort"
    7796                            />
    7897                        </div>
    79                         <div className="date-range-section item">
    80                             <Select
    81                                 defaultValue={{value: "all", label: "Lifetime"}}
    82                                 //value={selectedDateRange.value}
    83                                 //onChange={option => setSelectedDateRange(option.value)}
    84                                 options={dataRangeOptions}
    85                                 className="date-range sort"
    86                             />
    87                         </div>
     98                        <button onClick={filterJobAdvertisements} className="btn-open-modal">Find jobs</button>
    8899                    </div>
    89100                </div>
    90101            </div>
    91102            <div className="row row-cols-1 row-cols-md-4 g-4">
     103                <AddJobAdModal/>
    92104
    93105                {jobAdvertisementsByRecruiter &&
     
    97109                                <div className="card-head">
    98110                                    <span className="hourly-salary"><b>${jobAd.startingSalary}/hr</b></span>
    99                                     <span className="job-type"> {jobAd.jobType===JobType.JOB ? "Job" : "Internship"}</span>
     111                                    <span
     112                                        className="job-type"> {jobAd.jobType === JobType.JOB ? "Job" : "Internship"}</span>
    100113                                    {!jobAd.active && <span className="expired">Expired</span>}
    101114                                    <div className="card-management-btns">
     
    106119                                <div className="card-body">
    107120                                    <h5 className="card-title">{jobAd.title}</h5>
    108                                     <span>{jobAd.industry} • <span style={{color: "black", fontWeight: "bold"}}>{formatRelativeTime(jobAd.postedOn)}</span></span>
     121                                    <span>{jobAd.industry} • <span style={{
     122                                        color: "black",
     123                                        fontWeight: "bold"
     124                                    }}>{formatRelativeTime(jobAd.postedOn)}</span></span>
    109125                                    <div className="card-info">
    110                                         <span><i className="fa-solid fa-building" style={{color: "#000000"}}></i> Company: <span style={{color: "black", fontWeight: "bold"}}>{jobAd.recruiterName}</span></span> <br/>
     126                                        <span><i className="fa-solid fa-building"
     127                                                 style={{color: "#000000"}}></i> Company: <span style={{
     128                                            color: "black",
     129                                            fontWeight: "bold"
     130                                        }}>{jobAd.recruiterName}</span></span> <br/>
    111131                                    </div>
    112132
    113133                                    <div className="aligned">
    114                                         <Link to={`/my-job-advertisements/view/${jobAd.id}`} className="card-button solo">View applications</Link>
     134                                        <Link to={`/my-job-advertisements/${jobAd.id}/applications`}
     135                                              className="card-button solo">View applications</Link>
    115136                                    </div>
    116137
  • jobvista-frontend/src/views/selectOptions.js

    r19398ad r28b3398  
    5858
    5959export const sortOptions = [
    60     { value: "newest", label: "Date (Newest First)" },
    61     { value: "oldest", label: "Date (Oldest First)" },
    62     { value: "highest", label: "Salary (High to Low)" },
    63     { value: "lowest", label: "Salary (Low to High)" },
     60    { value: "date_newest", label: "Date (Newest First)" },
     61    { value: "date_oldest", label: "Date (Oldest First)" },
     62    { value: "salary_highest", label: "Salary (High to Low)" },
     63    { value: "salary_lowest", label: "Salary (Low to High)" },
    6464]
  • jobvista-frontend/src/views/static/Header.css

    r19398ad r28b3398  
    2020    height: 80px;
    2121    background-color: #535C91;
     22    font-family: Poppins, sans-serif;
    2223}
    2324
  • jobvista-frontend/src/views/static/Header.js

    r19398ad r28b3398  
    3838                        {role==Roles.JOBSEEKER &&
    3939                            <>
    40                                 <NavLink to="/applications" className="nav-item nav-link" >Applications</NavLink>
    41                                 <NavLink to="/favoritejobs" className="nav-item nav-link" >Saved</NavLink>
     40                                <NavLink to="/my-applications" className="nav-item nav-link" >My Applications</NavLink>
     41                                {/*<NavLink to="/favoritejobs" className="nav-item nav-link" >Saved</NavLink>*/}
    4242                            </>
    4343
     
    4545                        {role==Roles.RECRUITER &&
    4646                            <>
    47                                 <NavLink to="/my-job-advertisements" className="nav-item nav-link" >Job Advertisements</NavLink>
    48                                 <NavLink to="/favoritejobs" className="nav-item nav-link" >Saved</NavLink>
     47                                <NavLink to="/my-job-advertisements" className="nav-item nav-link" >My Advertisements</NavLink>
     48                                {/*<NavLink to="/favoritejobs" className="nav-item nav-link" >Saved</NavLink>*/}
    4949                            </>
    5050                        }
    5151                        <NavLink to="/about" className="nav-item nav-link">About</NavLink>
    52                         <NavLink to="/contact" className="nav-item nav-link">Contact</NavLink>
     52                        <NavLink to="/contact" className="nav-item nav-link">Support</NavLink>
    5353                    </ul>
    5454
Note: See TracChangeset for help on using the changeset viewer.