Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 6d56706

Browse files
committed
feat: support "interview" job candidate status
ref issue #114
1 parent 67dd586 commit 6d56706

File tree

4 files changed

+53
-45
lines changed

4 files changed

+53
-45
lines changed

src/constants/index.js

+25-19
Original file line numberDiff line numberDiff line change
@@ -86,34 +86,40 @@ export const CANDIDATE_STATUS = {
8686
SELECTED: "selected",
8787
SHORTLIST: "shortlist",
8888
REJECTED: "rejected",
89+
INTERVIEW: "interview",
8990
};
9091

9192
/**
92-
* Mapping between candidate status "server value" and human readable value
93+
* Candidate status filters keys
9394
*/
94-
export const CANDIDATE_STATUS_TO_TEXT = {
95-
[CANDIDATE_STATUS.OPEN]: "To Review",
96-
[CANDIDATE_STATUS.SELECTED]: "Selected",
97-
[CANDIDATE_STATUS.SHORTLIST]: "Interested",
98-
[CANDIDATE_STATUS.REJECTED]: "Not Interested",
95+
export const CANDIDATE_STATUS_FILTER_KEY = {
96+
TO_REVIEW: "TO_REVIEW",
97+
INTERESTED: "INTERESTED",
98+
NOT_INTERESTED: "NOT_INTERESTED",
9999
};
100100

101101
/**
102-
* Mapping between candidate status "server value" and list title text
103-
*/
104-
export const CANDIDATE_STATUS_TO_TITLE_TEXT = {
105-
[CANDIDATE_STATUS.OPEN]: "Candidates to Review",
106-
[CANDIDATE_STATUS.SHORTLIST]: "Interested Candidates",
107-
[CANDIDATE_STATUS.REJECTED]: "Not Interested Candidates",
108-
};
109-
110-
/**
111-
* Which candidate status filters to show on the open position details page
102+
* Candidate status filters
112103
*/
113104
export const CANDIDATE_STATUS_FILTERS = [
114-
CANDIDATE_STATUS.OPEN,
115-
CANDIDATE_STATUS.SHORTLIST,
116-
CANDIDATE_STATUS.REJECTED,
105+
{
106+
key: CANDIDATE_STATUS_FILTER_KEY.TO_REVIEW,
107+
buttonText: "To Review",
108+
title: "Candidates to Review",
109+
statuses: [CANDIDATE_STATUS.OPEN],
110+
},
111+
{
112+
key: CANDIDATE_STATUS_FILTER_KEY.INTERESTED,
113+
buttonText: "Interested",
114+
title: "Interested Candidates",
115+
statuses: [CANDIDATE_STATUS.SHORTLIST, CANDIDATE_STATUS.INTERVIEW],
116+
},
117+
{
118+
key: CANDIDATE_STATUS_FILTER_KEY.NOT_INTERESTED,
119+
buttonText: "Not Interested",
120+
title: "Not Interested Candidates",
121+
statuses: [CANDIDATE_STATUS.REJECTED],
122+
},
117123
];
118124

119125
/**

src/routes/PositionDetails/components/CandidatesStatusFilter/index.jsx

+9-9
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,28 @@ import Button from "components/Button";
99
import {
1010
CANDIDATE_STATUS,
1111
CANDIDATE_STATUS_FILTERS,
12-
CANDIDATE_STATUS_TO_TEXT,
12+
CANDIDATE_STATUS_FILTER_KEY,
1313
} from "constants";
1414

15-
const CandidatesStatusFilter = ({ currentStatus, onChange, candidates }) => {
15+
const CandidatesStatusFilter = ({ statusFilterKey, onChange, candidates }) => {
1616
return (
1717
<div styleName="candidates-status-filter">
18-
{CANDIDATE_STATUS_FILTERS.map((status) => (
18+
{CANDIDATE_STATUS_FILTERS.map((statusFilter) => (
1919
<Button
20-
key={status}
21-
type={currentStatus === status ? "segment-selected" : "segment"}
22-
onClick={() => onChange(status)}
20+
key={statusFilter.key}
21+
type={statusFilterKey === statusFilter.key ? "segment-selected" : "segment"}
22+
onClick={() => onChange(statusFilter)}
2323
>
24-
{CANDIDATE_STATUS_TO_TEXT[status]} (
25-
{_.filter(candidates, { status }).length})
24+
{statusFilter.buttonText} (
25+
{_.filter(candidates, (candidate) => statusFilter.statuses.includes(candidate.status)).length})
2626
</Button>
2727
))}
2828
</div>
2929
);
3030
};
3131

3232
CandidatesStatusFilter.propTypes = {
33-
currentStatus: PT.oneOf(Object.values(CANDIDATE_STATUS)),
33+
statusFilterKey: PT.oneOf(Object.values(CANDIDATE_STATUS_FILTER_KEY)),
3434
onChange: PT.func,
3535
candidates: PT.arrayOf(
3636
PT.shape({

src/routes/PositionDetails/components/PositionCandidates/index.jsx

+12-10
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import CardHeader from "components/CardHeader";
1010
import "./styles.module.scss";
1111
import Select from "components/Select";
1212
import {
13-
CANDIDATE_STATUS_TO_TITLE_TEXT,
13+
CANDIDATE_STATUS_FILTERS,
14+
CANDIDATE_STATUS_FILTER_KEY,
1415
CANDIDATES_SORT_OPTIONS,
1516
CANDIDATES_SORT_BY,
1617
CANDIDATE_STATUS,
@@ -54,19 +55,20 @@ const populateSkillsMatched = (position, candidate) => ({
5455
skillsMatched: _.intersectionBy(position.skills, candidate.skills, "id"),
5556
});
5657

57-
const PositionCandidates = ({ position, candidateStatus, updateCandidate }) => {
58+
const PositionCandidates = ({ position, statusFilterKey, updateCandidate }) => {
5859
const { candidates } = position;
5960
const [sortBy, setSortBy] = useState(CANDIDATES_SORT_BY.SKILL_MATCHED);
61+
const statusFilter = useMemo(() =>
62+
_.find(CANDIDATE_STATUS_FILTERS, { key: statusFilterKey })
63+
, [statusFilterKey]);
6064
const filteredCandidates = useMemo(
6165
() =>
6266
_.chain(candidates)
6367
.map((candidate) => populateSkillsMatched(position, candidate))
64-
.filter({
65-
status: candidateStatus,
66-
})
68+
.filter((candidate) => statusFilter.statuses.includes(candidate.status))
6769
.value()
6870
.sort(createSortCandidatesMethod(sortBy)),
69-
[candidates, candidateStatus, sortBy, position]
71+
[candidates, statusFilter, sortBy, position]
7072
);
7173

7274
const onSortByChange = useCallback(
@@ -139,7 +141,7 @@ const PositionCandidates = ({ position, candidateStatus, updateCandidate }) => {
139141
return (
140142
<div styleName="position-candidates">
141143
<CardHeader
142-
title={`${CANDIDATE_STATUS_TO_TITLE_TEXT[candidateStatus]} (${filteredCandidates.length})`}
144+
title={`${statusFilter.title} (${filteredCandidates.length})`}
143145
aside={
144146
<Select
145147
options={CANDIDATES_SORT_OPTIONS}
@@ -152,7 +154,7 @@ const PositionCandidates = ({ position, candidateStatus, updateCandidate }) => {
152154

153155
{filteredCandidates.length === 0 && (
154156
<div styleName="no-candidates">
155-
No {CANDIDATE_STATUS_TO_TITLE_TEXT[candidateStatus]}
157+
No {statusFilter.title}
156158
</div>
157159
)}
158160
{filteredCandidates.length > 0 && (
@@ -186,7 +188,7 @@ const PositionCandidates = ({ position, candidateStatus, updateCandidate }) => {
186188
)}
187189
</div>
188190
<div styleName="table-cell cell-action">
189-
{candidateStatus === CANDIDATE_STATUS.OPEN && (
191+
{statusFilterKey === CANDIDATE_STATUS_FILTER_KEY.TO_REVIEW && (
190192
<>
191193
Interested in this candidate?
192194
<div styleName="actions">
@@ -239,7 +241,7 @@ const PositionCandidates = ({ position, candidateStatus, updateCandidate }) => {
239241

240242
PositionCandidates.propType = {
241243
position: PT.object,
242-
candidateStatus: PT.oneOf(Object.values(CANDIDATE_STATUS)),
244+
statusFilterKey: PT.oneOf(Object.values(CANDIDATE_STATUS_FILTER_KEY)),
243245
};
244246

245247
export default PositionCandidates;

src/routes/PositionDetails/index.jsx

+7-7
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,25 @@ import PT from "prop-types";
88
import LayoutContainer from "components/LayoutContainer";
99
import LoadingIndicator from "components/LoadingIndicator";
1010
import PageHeader from "components/PageHeader";
11-
import { CANDIDATE_STATUS } from "constants";
11+
import { CANDIDATE_STATUS_FILTER_KEY } from "constants";
1212
import withAuthentication from "../../hoc/withAuthentication";
1313
import PositionCandidates from "./components/PositionCandidates";
1414
import CandidatesStatusFilter from "./components/CandidatesStatusFilter";
1515
import { useTeamPositionsState } from "./hooks/useTeamPositionsState";
1616
import "./styles.module.scss";
1717

1818
const PositionDetails = ({ teamId, positionId }) => {
19-
const [candidateStatus, setCandidateStatus] = useState(CANDIDATE_STATUS.OPEN);
19+
const [candidateStatusFilterKey, setCandidateStatusFilterKey] = useState(CANDIDATE_STATUS_FILTER_KEY.TO_REVIEW);
2020
const {
2121
state: { position, error },
2222
updateCandidate,
2323
} = useTeamPositionsState(teamId, positionId);
2424

2525
const onCandidateStatusChange = useCallback(
26-
(status) => {
27-
setCandidateStatus(status);
26+
(statusFilter) => {
27+
setCandidateStatusFilterKey(statusFilter.key);
2828
},
29-
[setCandidateStatus]
29+
[setCandidateStatusFilterKey]
3030
);
3131

3232
return (
@@ -41,14 +41,14 @@ const PositionDetails = ({ teamId, positionId }) => {
4141
aside={
4242
<CandidatesStatusFilter
4343
onChange={onCandidateStatusChange}
44-
currentStatus={candidateStatus}
44+
statusFilterKey={candidateStatusFilterKey}
4545
candidates={position.candidates}
4646
/>
4747
}
4848
/>
4949
<PositionCandidates
5050
position={position}
51-
candidateStatus={candidateStatus}
51+
statusFilterKey={candidateStatusFilterKey}
5252
updateCandidate={updateCandidate}
5353
/>
5454
</>

0 commit comments

Comments
 (0)