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

Commit a6340e2

Browse files
committed
Merge branch 'master' into dev
# Conflicts: # src/routes/PositionDetails/components/CandidatesStatusFilter/index.jsx # src/routes/PositionDetails/components/PositionCandidates/index.jsx # src/routes/PositionDetails/index.jsx
2 parents 8b94f2b + dc81fbc commit a6340e2

File tree

7 files changed

+107
-87
lines changed

7 files changed

+107
-87
lines changed

src/components/Babge/index.jsx

-22
This file was deleted.

src/components/Badge/index.jsx

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* Badge
3+
*
4+
* - type - see BADGE_TYPE values
5+
*
6+
*/
7+
import React from "react";
8+
import PT from "prop-types";
9+
import cn from "classnames";
10+
import { BADGE_TYPE } from "constants";
11+
import "./styles.module.scss";
12+
13+
const Badge = ({ children, type = BADGE_TYPE.PRIMARY }) => {
14+
return <span styleName={cn("badge", `type-${type}`)}>{children}</span>;
15+
};
16+
17+
Badge.propTypes = {
18+
children: PT.node,
19+
type: PT.oneOf(Object.values(BADGE_TYPE)),
20+
};
21+
22+
export default Badge;

src/components/Babge/styles.module.scss renamed to src/components/Badge/styles.module.scss

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
@import "styles/include";
22

3-
.babge {
3+
.badge {
44
@include font-roboto;
55
padding: 0 8px;
66
line-height: 20px;

src/constants/index.js

+27-21
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,9 @@ export const BUTTON_TYPE = {
7474
};
7575

7676
/**
77-
* Supported Babge Types
77+
* Supported Badge Types
7878
*/
79-
export const BABGE_TYPE = {
79+
export const BADGE_TYPE = {
8080
PRIMARY: "primary",
8181
DANGER: "danger",
8282
};
@@ -99,34 +99,40 @@ export const CANDIDATE_STATUS = {
9999
SELECTED: "selected",
100100
SHORTLIST: "shortlist",
101101
REJECTED: "rejected",
102+
INTERVIEW: "interview",
102103
};
103104

104105
/**
105-
* Mapping between candidate status "server value" and human readable value
106+
* Candidate status filters keys
106107
*/
107-
export const CANDIDATE_STATUS_TO_TEXT = {
108-
[CANDIDATE_STATUS.OPEN]: "To Review",
109-
[CANDIDATE_STATUS.SELECTED]: "Selected",
110-
[CANDIDATE_STATUS.SHORTLIST]: "Interested",
111-
[CANDIDATE_STATUS.REJECTED]: "Not Interested",
108+
export const CANDIDATE_STATUS_FILTER_KEY = {
109+
TO_REVIEW: "TO_REVIEW",
110+
INTERESTED: "INTERESTED",
111+
NOT_INTERESTED: "NOT_INTERESTED",
112112
};
113113

114114
/**
115-
* Mapping between candidate status "server value" and list title text
116-
*/
117-
export const CANDIDATE_STATUS_TO_TITLE_TEXT = {
118-
[CANDIDATE_STATUS.OPEN]: "Candidates to Review",
119-
[CANDIDATE_STATUS.SHORTLIST]: "Interested Candidates",
120-
[CANDIDATE_STATUS.REJECTED]: "Not Interested Candidates",
121-
};
122-
123-
/**
124-
* Which candidate status filters to show on the open position details page
115+
* Candidate status filters
125116
*/
126117
export const CANDIDATE_STATUS_FILTERS = [
127-
CANDIDATE_STATUS.OPEN,
128-
CANDIDATE_STATUS.SHORTLIST,
129-
CANDIDATE_STATUS.REJECTED,
118+
{
119+
key: CANDIDATE_STATUS_FILTER_KEY.TO_REVIEW,
120+
buttonText: "To Review",
121+
title: "Candidates to Review",
122+
statuses: [CANDIDATE_STATUS.OPEN],
123+
},
124+
{
125+
key: CANDIDATE_STATUS_FILTER_KEY.INTERESTED,
126+
buttonText: "Interested",
127+
title: "Interested Candidates",
128+
statuses: [CANDIDATE_STATUS.SHORTLIST, CANDIDATE_STATUS.INTERVIEW],
129+
},
130+
{
131+
key: CANDIDATE_STATUS_FILTER_KEY.NOT_INTERESTED,
132+
buttonText: "Not Interested",
133+
title: "Not Interested Candidates",
134+
statuses: [CANDIDATE_STATUS.REJECTED],
135+
},
130136
];
131137

132138
/**

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

+21-17
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,39 @@ import PT from "prop-types";
66
import "./styles.module.scss";
77
import _ from "lodash";
88
import Button from "components/Button";
9-
import Babge from "components/Babge";
9+
import Badge from "components/Badge";
1010
import {
1111
CANDIDATE_STATUS,
1212
CANDIDATE_STATUS_FILTERS,
13-
CANDIDATE_STATUS_TO_TEXT,
13+
CANDIDATE_STATUS_FILTER_KEY,
1414
} from "constants";
1515

16-
const CandidatesStatusFilter = ({ currentStatus, onChange, candidates }) => {
16+
const CandidatesStatusFilter = ({ statusFilterKey, onChange, candidates }) => {
1717
return (
1818
<div styleName="candidates-status-filter">
19-
{CANDIDATE_STATUS_FILTERS.map((status, index) => (
20-
<Button
21-
key={status}
22-
type={currentStatus === status ? "segment-selected" : "segment"}
23-
onClick={() => onChange(status)}
24-
>
25-
{CANDIDATE_STATUS_TO_TEXT[status]} (
26-
{_.filter(candidates, { status }).length})
27-
{index === 0 && _.filter(candidates, { status }).length ? (
28-
<Babge type="danger">Pending</Babge>
29-
) : null}
30-
</Button>
31-
))}
19+
{CANDIDATE_STATUS_FILTERS.map((statusFilter) => {
20+
const count = _.filter(candidates, (candidate) => statusFilter.statuses.includes(candidate.status)).length;
21+
22+
return (
23+
<Button
24+
key={statusFilter.key}
25+
type={statusFilterKey === statusFilter.key ? "segment-selected" : "segment"}
26+
onClick={() => onChange(statusFilter)}
27+
>
28+
{statusFilter.buttonText} (
29+
{count})
30+
{statusFilter.key === CANDIDATE_STATUS_FILTER_KEY.TO_REVIEW && count > 0 && (
31+
<Badge type="danger">Pending</Badge>
32+
)}
33+
</Button>
34+
)
35+
})}
3236
</div>
3337
);
3438
};
3539

3640
CandidatesStatusFilter.propTypes = {
37-
currentStatus: PT.oneOf(Object.values(CANDIDATE_STATUS)),
41+
statusFilterKey: PT.oneOf(Object.values(CANDIDATE_STATUS_FILTER_KEY)),
3842
onChange: PT.func,
3943
candidates: PT.arrayOf(
4044
PT.shape({

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

+16-14
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,
@@ -57,24 +58,21 @@ const populateSkillsMatched = (position, candidate) => ({
5758
skillsMatched: _.intersectionBy(position.skills, candidate.skills, "id"),
5859
});
5960

60-
const PositionCandidates = ({ position, candidateStatus, updateCandidate }) => {
61+
const PositionCandidates = ({ position, statusFilterKey, updateCandidate }) => {
6162
const { candidates } = position;
6263
const [sortBy, setSortBy] = useState(CANDIDATES_SORT_BY.SKILL_MATCHED);
63-
64-
useEffect(() => {
65-
setPage(1);
66-
}, [candidateStatus]);
64+
const statusFilter = useMemo(() =>
65+
_.find(CANDIDATE_STATUS_FILTERS, { key: statusFilterKey })
66+
, [statusFilterKey]);
6767

6868
const filteredCandidates = useMemo(
6969
() =>
7070
_.chain(candidates)
7171
.map((candidate) => populateSkillsMatched(position, candidate))
72-
.filter({
73-
status: candidateStatus,
74-
})
72+
.filter((candidate) => statusFilter.statuses.includes(candidate.status))
7573
.value()
7674
.sort(createSortCandidatesMethod(sortBy)),
77-
[candidates, candidateStatus, sortBy, position]
75+
[candidates, statusFilter, sortBy, position]
7876
);
7977

8078
const onSortByChange = useCallback(
@@ -94,6 +92,10 @@ const PositionCandidates = ({ position, candidateStatus, updateCandidate }) => {
9492
setPage(newPage);
9593
}, [perPage, setPerPage, page, setPage]);
9694

95+
useEffect(() => {
96+
setPage(1);
97+
}, [statusFilterKey]);
98+
9799
const pagesTotal = Math.ceil(filteredCandidates.length / perPage);
98100

99101
const pageCandidates = useMemo(
@@ -147,7 +149,7 @@ const PositionCandidates = ({ position, candidateStatus, updateCandidate }) => {
147149
return (
148150
<div styleName="position-candidates">
149151
<CardHeader
150-
title={`${CANDIDATE_STATUS_TO_TITLE_TEXT[candidateStatus]} (${filteredCandidates.length})`}
152+
title={`${statusFilter.title} (${filteredCandidates.length})`}
151153
aside={
152154
<Select
153155
options={CANDIDATES_SORT_OPTIONS}
@@ -160,7 +162,7 @@ const PositionCandidates = ({ position, candidateStatus, updateCandidate }) => {
160162

161163
{filteredCandidates.length === 0 && (
162164
<div styleName="no-candidates">
163-
No {CANDIDATE_STATUS_TO_TITLE_TEXT[candidateStatus]}
165+
No {statusFilter.title}
164166
</div>
165167
)}
166168
{filteredCandidates.length > 0 && (
@@ -194,7 +196,7 @@ const PositionCandidates = ({ position, candidateStatus, updateCandidate }) => {
194196
)}
195197
</div>
196198
<div styleName="table-cell cell-action">
197-
{candidateStatus === CANDIDATE_STATUS.OPEN && hasPermission(PERMISSIONS.UPDATE_JOB_CANDIDATE) && (
199+
{statusFilterKey === CANDIDATE_STATUS_FILTER_KEY.TO_REVIEW && hasPermission(PERMISSIONS.UPDATE_JOB_CANDIDATE) && (
198200
<>
199201
Interested in this candidate?
200202
<div styleName="actions">
@@ -247,7 +249,7 @@ const PositionCandidates = ({ position, candidateStatus, updateCandidate }) => {
247249

248250
PositionCandidates.propType = {
249251
position: PT.object,
250-
candidateStatus: PT.oneOf(Object.values(CANDIDATE_STATUS)),
252+
statusFilterKey: PT.oneOf(Object.values(CANDIDATE_STATUS_FILTER_KEY)),
251253
};
252254

253255
export default PositionCandidates;

src/routes/PositionDetails/index.jsx

+20-12
Original file line numberDiff line numberDiff line change
@@ -8,38 +8,46 @@ import PT from "prop-types";
88
import Page from "components/Page";
99
import LoadingIndicator from "components/LoadingIndicator";
1010
import PageHeader from "components/PageHeader";
11-
import { CANDIDATE_STATUS } from "constants";
11+
import {
12+
CANDIDATE_STATUS_FILTER_KEY,
13+
CANDIDATE_STATUS_FILTERS,
14+
} from "constants";
1215
import withAuthentication from "../../hoc/withAuthentication";
1316
import PositionCandidates from "./components/PositionCandidates";
1417
import CandidatesStatusFilter from "./components/CandidatesStatusFilter";
1518
import { useTeamPositionsState } from "./hooks/useTeamPositionsState";
1619
import "./styles.module.scss";
1720

21+
const inReviewStatusFilter = _.find(CANDIDATE_STATUS_FILTERS, {
22+
key: CANDIDATE_STATUS_FILTER_KEY.TO_REVIEW,
23+
});
24+
1825
const PositionDetails = ({ teamId, positionId }) => {
19-
// be dafault show "Interested" tab
20-
const [candidateStatus, setCandidateStatus] = useState(
21-
CANDIDATE_STATUS.SHORTLIST
26+
// by default show "interested" tab
27+
const [candidateStatusFilterKey, setCandidateStatusFilterKey] = useState(
28+
CANDIDATE_STATUS_FILTER_KEY.INTERESTED
2229
);
2330
const {
2431
state: { position, error },
2532
updateCandidate,
2633
} = useTeamPositionsState(teamId, positionId);
2734

2835
const onCandidateStatusChange = useCallback(
29-
(status) => {
30-
setCandidateStatus(status);
36+
(statusFilter) => {
37+
setCandidateStatusFilterKey(statusFilter.key);
3138
},
32-
[setCandidateStatus]
39+
[setCandidateStatusFilterKey]
3340
);
3441

3542
// if there are some candidates to review, then show "To Review" tab by default
3643
useEffect(() => {
3744
if (
3845
position &&
39-
_.filter(position.candidates, { status: CANDIDATE_STATUS.OPEN }).length >
40-
0
46+
_.filter(position.candidates, (candidate) =>
47+
inReviewStatusFilter.statuses.includes(candidate.status)
48+
).length > 0
4149
) {
42-
setCandidateStatus(CANDIDATE_STATUS.OPEN);
50+
setCandidateStatusFilterKey(CANDIDATE_STATUS_FILTER_KEY.TO_REVIEW);
4351
}
4452
}, [position]);
4553

@@ -55,14 +63,14 @@ const PositionDetails = ({ teamId, positionId }) => {
5563
aside={
5664
<CandidatesStatusFilter
5765
onChange={onCandidateStatusChange}
58-
currentStatus={candidateStatus}
66+
statusFilterKey={candidateStatusFilterKey}
5967
candidates={position.candidates}
6068
/>
6169
}
6270
/>
6371
<PositionCandidates
6472
position={position}
65-
candidateStatus={candidateStatus}
73+
statusFilterKey={candidateStatusFilterKey}
6674
updateCandidate={updateCandidate}
6775
/>
6876
</>

0 commit comments

Comments
 (0)