diff --git a/frontend/src/App.js b/frontend/src/App.js index 02e21c3..25bbbf8 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -4,6 +4,7 @@ import JobList from "./components/JobList"; import CompanyForm from "./components/CompanyForm"; import JobForm from "./components/JobForm"; +import CompanyJobs from "./components/CompanyJobs"; function App() { //const [companies, setCompanies]=useState([]); @@ -37,6 +38,7 @@ } /> } /> } /> + } /> ); diff --git a/frontend/src/components/CompanyJobs.js b/frontend/src/components/CompanyJobs.js new file mode 100644 index 0000000..9a3c22b --- /dev/null +++ b/frontend/src/components/CompanyJobs.js @@ -0,0 +1,59 @@ +import React from 'react'; +import { Link, useParams } from "react-router-dom"; +import { apiDelete, apiGet } from '../api'; + +function CompanyJobs() { + const [jobs, setJobs] = React.useState([]); + const [error,setError]=React.useState(null); + const {id}=useParams(); + React.useEffect(() => { + apiGet(`/api/companies/${id}/jobs`) + .then(setJobs) + .catch(error => setError(error.message || "Failed to fetch jobs")); + }, []); + + const handleDelete = async (id) => { + if(window.confirm("Are you sure you want to delete this job?")) { + try { + await apiDelete(`/api/jobs/${id}`); + setJobs(prev => prev.filter(j => j.id !== id)); // update local state + } catch (err) { + setError(err.message || "Failed to delete job"); + } + } + }; + + return ( + + + Back to Companies + + {error && {error}} + + + + Role + Company + Status + Actions + + + + {jobs.map(job => ( + + {job.role} + {job.company?.name || "-"} + {job.status} + + Edit + handleDelete(job.id)} className="btn btn-sm btn-danger">Delete + + + ))} + + + + ); +} + +export default CompanyJobs; \ No newline at end of file diff --git a/src/main/java/com/kpaudel/controller/ApiController.java b/src/main/java/com/kpaudel/controller/ApiController.java index 384adc9..bedac12 100644 --- a/src/main/java/com/kpaudel/controller/ApiController.java +++ b/src/main/java/com/kpaudel/controller/ApiController.java @@ -79,6 +79,10 @@ .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); } + @GetMapping("/companies/{id}/jobs") + public List listJobsByCompany(@PathVariable UUID id) { + return jobRepo.findByCompanyId(id); + } @PostMapping("/companies") public Company createCompany(@RequestBody Company company) { diff --git a/src/test/java/com/kpaudel/controller/ApiControllerTest.java b/src/test/java/com/kpaudel/controller/ApiControllerTest.java new file mode 100644 index 0000000..e90d64c --- /dev/null +++ b/src/test/java/com/kpaudel/controller/ApiControllerTest.java @@ -0,0 +1,96 @@ +package com.kpaudel.controller; + +import com.kpaudel.model.ApplicationStatus; +import com.kpaudel.model.Company; +import com.kpaudel.model.JobApplication; +import com.kpaudel.repository.CompanyRepository; +import com.kpaudel.repository.JobApplicationRepository; +import com.kpaudel.service.JobService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; + +@WebMvcTest(ApiController.class) +public class ApiControllerTest { + @Autowired + private MockMvc mockMvc; + @MockBean + private JobService jobService; + @MockBean + private CompanyRepository companyRepository; + + @MockBean + private JobApplicationRepository jobApplicationRepository; + + @Test + public void testListJobs() throws Exception { + Company testCompnay = new Company(); + testCompnay.setId(UUID.randomUUID()); + testCompnay.setName("Home-Lab"); + testCompnay.setWebsite("https://home-lab.com"); + + JobApplication softwareApplication = JobApplication.builder() + .id(UUID.randomUUID()) + .role("Software Engineer") + .location("Remote") + .company(testCompnay) + .status(ApplicationStatus.APPLIED) + .build(); + JobApplication devOpsEngineer = JobApplication.builder() + .id(UUID.randomUUID()) + .role("DevOps Engineer") + .location("New York, NY") + .company(testCompnay) + .status(ApplicationStatus.INTERESTED) + .build(); + + when(this.jobApplicationRepository.findAll()).thenReturn(List.of(softwareApplication,devOpsEngineer)); + mockMvc.perform(get("/api/jobs")).andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$.length()").value(2)) + .andExpect(jsonPath("$[0].role").value("Software Engineer")); + } + + @Test + public void testListJobsByCompany() throws Exception { + Company testCompnay = new Company(); + testCompnay.setId(UUID.randomUUID()); + testCompnay.setName("Home-Lab"); + testCompnay.setWebsite("https://home-lab.com"); + + List applications = new ArrayList<>(); + applications.add(JobApplication.builder() + .id(UUID.randomUUID()) + .role("Software Engineer") + .location("Remote") + .company(testCompnay) + .status(ApplicationStatus.APPLIED) + .build()); + applications.add(JobApplication.builder() + .id(UUID.randomUUID()) + .role("DevOps Engineer") + .location("New York, NY") + .company(testCompnay) + .status(ApplicationStatus.INTERESTED) + .build()); + when(this.jobApplicationRepository.findByCompanyId(testCompnay.getId())).thenReturn(applications); + mockMvc.perform(get("/api/companies/"+testCompnay.getId()+"/jobs")).andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$.length()").value(2)) + .andExpect(jsonPath("$[0].role").value("Software Engineer")); + } +}