Skip to content

Commit f387392

Browse files
authored
Merge pull request #111 from is212g7t5/ailys/G7T5-197
[G7T5-9 & G7T5-197] HR Add and Remove Skills from Job Role
2 parents b3248b9 + 9ba4d68 commit f387392

14 files changed

+273
-62
lines changed

app/src/api/jobSkill.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,34 @@ function extractSkillIdsFromJobSkills(jobSkills) {
5454
});
5555
return skillIdArray;
5656
}
57+
58+
export const createJobSkill = async (jobId, skillId) => {
59+
try {
60+
const res = await axiosJobSkillInstance.post("/", {
61+
job_id: jobId,
62+
skill_id: skillId
63+
});
64+
if (res) {
65+
console.log(res.data);
66+
return res.data;
67+
}
68+
throw new Error("No data returned from backend");
69+
} catch (error) {
70+
console.log(error);
71+
return [];
72+
}
73+
}
74+
75+
export const deleteAllSkillsUnderJob = async (jobId) => {
76+
try {
77+
const res = await axiosJobSkillInstance.delete(`/${jobId}`);
78+
if (res) {
79+
console.log(res.data);
80+
return res.data;
81+
}
82+
throw new Error("No data returned from backend");
83+
} catch (error) {
84+
console.log(error);
85+
return [];
86+
}
87+
}

app/src/components/course/CourseDescription.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export default function CourseDescription({ courseDesc, skills }) {
1313
<div className='m-auto flex flex-col w-full p-5 px-10 bg-gray-100 rounded-lg'>
1414
<p className='text-ellipsis overflow-hidden font-medium text-justify'>{courseDesc}</p>
1515
{skills.length ? (
16-
<div className='flex-grid mt-5'>{renderActiveSkills}</div>
16+
<div className='flex flex-wrap mt-5'>{renderActiveSkills}</div>
1717
) : (
1818
"No skills attached to this course"
1919
)}

app/src/components/course/SkillBadge.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from "react";
22

33
export default function SkillBadge({ skillName }) {
44
return (
5-
<span className='bg-accent4 text-accent1 text-sm font-semibold mr-2 px-2.5 py-0.5 rounded'>
5+
<span className='bg-accent4 text-accent1 text-sm font-semibold mr-2 px-2.5 py-0.5 rounded m-1'>
66
{skillName}
77
</span>
88
);

app/src/components/job/JobTile.jsx

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ function CreateEditJobButton({ handleEditJobButtonClick }) {
104104
);
105105
}
106106

107-
function CreateDeleteJobButton({showPopUp}){
107+
function CreateDeleteJobButton({ showPopUp }) {
108108
return (
109109
<button
110110
type='button'
@@ -119,9 +119,7 @@ function CreateDeleteJobButton({showPopUp}){
119119

120120
function CreateInactiveBadge() {
121121
return (
122-
<span className='w-fit bg-gray-100 text-gray-400 mr-2 px-2.5 py-0.5 rounded'>
123-
Inactive
124-
</span>
122+
<span className='w-fit bg-gray-100 text-gray-400 mr-2 px-2.5 py-0.5 rounded'>Inactive</span>
125123
);
126124
}
127125

@@ -150,14 +148,22 @@ function JobTileButton({ isDetailsOpen, setIsDetailsOpen }) {
150148
}
151149

152150
function JobTileDescription({ jobDesc, skills }) {
153-
const renderSkillsForJobRole = skills.map(({ skillId, skillName, skillDesc }, index) => (
154-
<SkillBadge key={`skill-${skillId}`} skillName={skillName} />
155-
));
151+
let numberOfInactiveSkills = 0;
152+
153+
const renderSkillsForJobRole = skills.map(
154+
({ skillId, skillName, skillDesc, isActive }, index) => {
155+
if (isActive) {
156+
return <SkillBadge key={`skill-${skillId}`} skillName={skillName} />;
157+
}
158+
numberOfInactiveSkills += 1;
159+
return null;
160+
},
161+
);
156162

157163
return (
158164
<div className='m-auto flex flex-col w-full p-5 px-10 bg-gray-100 rounded-lg'>
159165
<p className='text-ellipsis overflow-hidden font-medium text-justify'>{jobDesc}</p>
160-
{skills.length ? (
166+
{skills.length && numberOfInactiveSkills !== skills.length ? (
161167
<div className='flex flex-wrap mt-5'>{renderSkillsForJobRole}</div>
162168
) : (
163169
"No current skills"

app/src/components/job/hr/CreateJob.jsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
import { useState } from "react";
22
import { createJob } from "src/api/jobs";
3+
import { createJobSkill } from "src/api/jobSkill";
34
import { useUserContext } from "src/contexts/UserContext";
45
import CreateJobSuccess from "./CreateJobSuccess";
56
import JobNameInput from "./form/JobNameInput";
67
import JobDescTextArea from "./form/JobDescTextArea";
8+
import JobSkillSelection from "./form/JobSkillSelection";
79

810
export default function HRCreateJob() {
911
const { currentUserType } = useUserContext();
1012
const [jobName, setJobName] = useState("");
1113
const [jobDesc, setJobDesc] = useState("");
1214
const [errors, setErrors] = useState([]);
1315
const [displayPopup, setDisplayPopup] = useState(false);
16+
const [selectedSkills, setSelectedSkills] = useState([]);
1417

1518
const handleSubmit = async (e) => {
1619
e.preventDefault();
@@ -26,6 +29,9 @@ export default function HRCreateJob() {
2629
setErrors(errorList);
2730
} else {
2831
setErrors([]);
32+
selectedSkills.forEach(async (skillId) => {
33+
await createJobSkill(res.job_id, skillId);
34+
});
2935
setDisplayPopup(true);
3036
}
3137
};
@@ -36,6 +42,7 @@ export default function HRCreateJob() {
3642
setJobName("");
3743
setJobDesc("");
3844
setErrors([]);
45+
setSelectedSkills([]);
3946
setDisplayPopup(false);
4047
};
4148

@@ -46,11 +53,18 @@ export default function HRCreateJob() {
4653
<h1 className='text-3xl text-left font-bold'>Create New Job</h1>
4754
<form onSubmit={handleSubmit} className='pt-10'>
4855
<div className='mb-6'>
49-
<JobNameInput jobName={jobName} setJobName={setJobName} />
56+
<JobNameInput jobName={jobName} setJobName={setJobName} jobIsActive />
5057
</div>
5158
<div className='mb-6'>
52-
<JobDescTextArea jobDesc={jobDesc} setJobDesc={setJobDesc} />
59+
<JobDescTextArea jobDesc={jobDesc} setJobDesc={setJobDesc} jobIsActive />
5360
</div>
61+
62+
<JobSkillSelection
63+
selectedSkills={selectedSkills}
64+
setSelectedSkills={setSelectedSkills}
65+
jobIsActive
66+
/>
67+
5468
<button
5569
type='submit'
5670
className='text-white bg-accent2 hover:bg-accent3 focus:ring-2 focus:ring-gray-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center'

app/src/components/job/hr/CreateJobSuccess.jsx

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { useHistory } from "react-router-dom";
2-
import { CheckCircleIcon } from "@heroicons/react/20/solid";
32

43
export default function CreateJobSuccess({ jobName, resetFields }) {
54
const redirectToCreateJobPage = () => {
@@ -14,28 +13,35 @@ export default function CreateJobSuccess({ jobName, resetFields }) {
1413

1514
return (
1615
<div
17-
className='absolute flex flex-col justify-center space-y-5 mx-auto my-auto w-1/2 h-1/2 bg-gray-100 z-10 inset-0 shadow-lg rounded-lg items-center text-center'
16+
className='flex justify-center fixed inset-0 h-screen items-center z-10 backdrop-grayscale backdrop-blur-xl p-5'
1817
aria-labelledby='modal-title'
1918
role='dialog'
2019
aria-modal='true'
2120
>
22-
<CheckCircleIcon className='mx-auto h-20 w-20 text-green-500' aria-hidden='true' />
23-
<p className=''>Job {jobName} has been successfully created!</p>
24-
<div className='flex space-x-5'>
25-
<button
26-
type='button'
27-
className='relative inline-flex items-center rounded-md border border-accent2 bg-transparent px-4 py-2 text-sm font-medium text-accent2 shadow-sm hover:text-white hover:bg-accent3 hover:border-accent3 focus:outline-none focus:ring-2 focus:ring-gray-300'
28-
onClick={redirectToCreateJobPage}
29-
>
30-
<span>Create Another Job</span>
31-
</button>
32-
<button
33-
type='button'
34-
className='relative inline-flex items-center rounded-md border border-transparent bg-accent2 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-accent3 focus:outline-none focus:ring-2 focus:ring-gray-300'
35-
onClick={redirectToJobPage}
36-
>
37-
<span>Return to Jobs</span>
38-
</button>
21+
<div className='flex-initial'>
22+
<div className='container shadow-lg px-7 py-5 grid rounded-lg bg-white'>
23+
<div className='grid-row py-3 text-3xl font-bold'>Note</div>
24+
25+
<div className='grid-row py-3 text-lg'>{jobName} has been successfully created!</div>
26+
27+
<div className='grid-row py-3 flex justify-end'>
28+
<button
29+
type='button'
30+
className='text-white bg-accent2 hover:bg-accent3 font-medium rounded-lg text-sm px-5 py-2.5 mr-2 mb-2'
31+
onClick={redirectToCreateJobPage}
32+
>
33+
Create Another Job
34+
</button>
35+
36+
<button
37+
type='button'
38+
className='text-white bg-secondary hover:bg-primary font-medium rounded-lg text-sm px-5 py-2.5 mr-2 mb-2'
39+
onClick={redirectToJobPage}
40+
>
41+
Return to Jobs
42+
</button>
43+
</div>
44+
</div>
3945
</div>
4046
</div>
4147
);

app/src/components/job/hr/UpdateJob.jsx

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { useState } from "react";
22
import { updateJob } from "src/api/jobs";
3+
import { createJobSkill, deleteAllSkillsUnderJob } from "src/api/jobSkill";
34
import { useUpdateJobContext } from "src/contexts/UpdateJobContext";
45
import { useUserContext } from "src/contexts/UserContext";
56
import UpdateJobSuccess from "./UpdateJobSuccess";
67
import JobNameInput from "./form/JobNameInput";
78
import JobDescTextArea from "./form/JobDescTextArea";
89
import JobIsActiveToggle from "./form/JobIsActiveToggle";
10+
import JobSkillSelection from "./form/JobSkillSelection";
911

1012
export default function HRUpdateJob() {
1113
const { currentUserType } = useUserContext();
@@ -16,25 +18,33 @@ export default function HRUpdateJob() {
1618
const [jobIsActive, setJobIsActive] = useState(updateJobRole.isActive);
1719
const [errors, setErrors] = useState([]);
1820
const [displayPopup, setDisplayPopup] = useState(false);
21+
const [selectedSkills, setSelectedSkills] = useState([]);
1922

2023
const handleSubmit = async (e) => {
2124
e.preventDefault();
2225
const res = await updateJob(updateJobRole.jobId, jobName, jobDesc, jobIsActive);
2326
if (res.detail) {
2427
const errorList = [];
25-
if (res.detail.map) {
28+
if (Array.isArray(res.detail)) {
2629
res.detail.map((errorMsg) => errorList.push(errorMsg.msg));
2730
} else {
2831
errorList.push(res.detail);
2932
}
3033
setErrors(errorList);
34+
} else if (jobIsActive === false || jobIsActive === 0) {
35+
setDisplayPopup(true);
3136
} else {
3237
setErrors([]);
38+
await deleteAllSkillsUnderJob(updateJobRole.jobId);
39+
selectedSkills.map(async (skillId) => {
40+
await createJobSkill(res.job_id, skillId);
41+
});
3342
setDisplayPopup(true);
3443
}
3544
};
3645

37-
const renderErrors = errors && errors.map && errors.map((error) => <p>{error}</p>);
46+
const renderErrors =
47+
errors && errors.map && errors.map((error, index) => <p key={index}>{error}</p>);
3848

3949
switch (currentUserType) {
4050
case "HR":
@@ -43,11 +53,23 @@ export default function HRUpdateJob() {
4353
<h1 className='text-3xl text-left font-bold'>Update Job</h1>
4454
<form onSubmit={handleSubmit} className='pt-10'>
4555
<div className='mb-6'>
46-
<JobNameInput jobName={jobName} setJobName={setJobName} />
56+
<JobNameInput jobName={jobName} setJobName={setJobName} jobIsActive={jobIsActive} />
4757
</div>
4858
<div className='mb-6'>
49-
<JobDescTextArea jobDesc={jobDesc} setJobDesc={setJobDesc} />
59+
<JobDescTextArea
60+
jobDesc={jobDesc}
61+
setJobDesc={setJobDesc}
62+
jobIsActive={jobIsActive}
63+
/>
5064
</div>
65+
66+
<JobSkillSelection
67+
selectedSkills={selectedSkills}
68+
setSelectedSkills={setSelectedSkills}
69+
jobIsActive={jobIsActive}
70+
jobId={updateJobRole.jobId}
71+
/>
72+
5173
<div className='mb-6'>
5274
<p className='block mb-2 text-md font-medium text-black'>Job Status</p>
5375
<JobIsActiveToggle jobIsActive={jobIsActive} setJobIsActive={setJobIsActive} />
@@ -60,7 +82,7 @@ export default function HRUpdateJob() {
6082
</button>
6183
</form>
6284
<div className='pt-5 text-red-500'>{renderErrors}</div>
63-
{displayPopup ? <UpdateJobSuccess jobName={jobName} /> : null}
85+
{displayPopup ? <UpdateJobSuccess jobName={jobName} jobIsActive={jobIsActive} /> : null}
6486
</div>
6587
);
6688
default:
Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { useHistory } from "react-router-dom";
2-
import { CheckCircleIcon } from "@heroicons/react/20/solid";
32

4-
export default function UpdateJobSuccess({ jobName }) {
3+
export default function UpdateJobSuccess({ jobName, jobIsActive }) {
54
const history = useHistory();
65

76
const redirectToJobPage = () => {
@@ -10,20 +9,34 @@ export default function UpdateJobSuccess({ jobName }) {
109

1110
return (
1211
<div
13-
className='absolute flex flex-col justify-center space-y-5 mx-auto my-auto w-1/2 h-1/2 bg-gray-100 z-10 inset-0 shadow-lg rounded-lg items-center text-center'
12+
className='flex justify-center fixed inset-0 h-screen items-center z-10 backdrop-grayscale backdrop-blur-xl p-5'
1413
aria-labelledby='modal-title'
1514
role='dialog'
1615
aria-modal='true'
1716
>
18-
<CheckCircleIcon className='mx-auto h-20 w-20 text-green-500' aria-hidden='true' />
19-
<p className=''>Job {jobName} has been successfully updated!</p>
20-
<button
21-
type='button'
22-
className='relative inline-flex items-center rounded-md border border-transparent bg-accent2 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-accent3 focus:outline-none focus:ring-2 focus:ring-gray-300'
23-
onClick={redirectToJobPage}
24-
>
25-
<span>Return to Jobs</span>
26-
</button>
17+
<div className='flex-initial'>
18+
<div className='container shadow-lg px-7 py-5 grid rounded-lg bg-white'>
19+
<div className='grid-row py-3 text-3xl font-bold'>Note</div>
20+
21+
{jobIsActive === false || jobIsActive === 0 ? (
22+
<div className='grid-row py-3 text-lg'>
23+
{jobName} is toggled to inactive. No other changes will be saved.
24+
</div>
25+
) : (
26+
<div className='grid-row py-3 text-lg'>{jobName} has been successfully updated!</div>
27+
)}
28+
29+
<div className='grid-row py-3 flex justify-end'>
30+
<button
31+
type='button'
32+
className='text-white bg-secondary hover:bg-primary font-medium rounded-lg text-sm px-5 py-2.5 mr-2 mb-2'
33+
onClick={redirectToJobPage}
34+
>
35+
Return to Jobs
36+
</button>
37+
</div>
38+
</div>
39+
</div>
2740
</div>
2841
);
2942
}

app/src/components/job/hr/form/JobDescTextArea.jsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export default function JobDescTextArea({ jobDesc, setJobDesc }) {
1+
export default function JobDescTextArea({ jobDesc, setJobDesc, jobIsActive }) {
22
const handleJobDescChange = (e) => {
33
e.preventDefault();
44
if (e.target.value.length <= 255) {
@@ -20,9 +20,12 @@ export default function JobDescTextArea({ jobDesc, setJobDesc }) {
2020
rows={5}
2121
value={jobDesc}
2222
onChange={handleJobDescChange}
23-
className='bg-gray-100 border border-gray-300 text-black text-sm rounded-lg focus:ring-gray-400 focus:border-gray-500 block w-full p-2.5'
23+
className={`bg-gray-100 border border-gray-300 ${
24+
jobIsActive === false || jobIsActive === 0 ? "text-gray-500" : "text-black"
25+
} text-sm rounded-lg focus:ring-gray-400 focus:border-gray-500 block w-full p-2.5`}
26+
disabled={jobIsActive === false || jobIsActive === 0}
2427
/>
2528
<p className='text-right text-sm'>{jobDesc.length}/255</p>
2629
</label>
27-
)
30+
);
2831
}

app/src/components/job/hr/form/JobIsActiveToggle.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export default function JobIsActiveToggle({ jobIsActive, setJobIsActive }) {
1515
id='checked-toggle'
1616
className='sr-only peer'
1717
checked={jobIsActive}
18+
readOnly
1819
/>
1920
<div className="w-11 h-6 bg-gray-100 rounded-full peer peer-focus:ring-2 peer-focus:ring-gray-300 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-accent3" />
2021
<span className='ml-3 text-sm font-medium text-black'>

0 commit comments

Comments
 (0)