Przeglądaj źródła

Merge branch 'feature/1823_fixing_filter_applicants_bug-fe' of Neca/HRCenter into FE_dev

pull/69/head
safet.purkovic 3 lat temu
rodzic
commit
f26f46e0f3

+ 117
- 54
src/components/Candidates/CandidateFilters.js Wyświetl plik

@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import PropType from "prop-types";
import Box from "@mui/material/Box";
import Drawer from "@mui/material/Drawer";
@@ -8,44 +8,59 @@ import Checkbox from "@mui/material/Checkbox";
import Slider from "@mui/material/Slider";
import filterIcon from "../../assets/images/filter_vector.png";
import x from "../../assets/images/x.png";
import { filterCandidates } from "../../store/actions/candidates/candidatesActions";
import {
filterCandidates,
fetchAdsCandidates
} from "../../store/actions/candidates/candidatesActions";
import { useDispatch } from "react-redux";
import { formatDateInput } from "../../util/helpers/dateHelpers";
import { changeIsCheckedValue,resetIsCheckedValue } from "../../store/actions/technologies/technologiesActions";

const CandidateFilters = ({ open, handleClose,pageSize,currentPage }) => {
const CandidateFilters = ({
open,
handleClose,
pageSize,
currentPage,
isTableView,
technologies,
}) => {
const dispatch = useDispatch();
const [sliderValue, setSliderValue] = useState([0, 0]);
const [startingDate,setStartingDate] = useState('')
const [endingDate,setEndingDate] = useState('')
const [technologies, setTechnologies] = useState([
{ name: "react", isChecked: false },
{ name: "angular", isChecked: false },
{ name: "sql", isChecked: false },
{ name: "nodejs", isChecked: false },
{ name: ".net", isChecked: false },
{ name: "git", isChecked: false },
{ name: "html/css", isChecked: false },
{ name: "vanillaJS", isChecked: false },
]);
const [startingDate, setStartingDate] = useState("");
const [endingDate, setEndingDate] = useState("");
const [typesOfEmployments, setTypesOfEmployments] = useState([
{ name: "Posao", isChecked: true },
{ name: "Posao", isChecked: false },
{ name: "Intership", isChecked: false },
]);
const [isInitial, setIsInitial] = useState(true)

useEffect(() => {
console.log(isTableView);
}, [isTableView]);

const handleSliderChange = (_, newValue) => {
setSliderValue(newValue);
};

const updateState = (name, value) => {
const newState = technologies.map((obj) => {
if (obj.name === name) {
return { ...obj, isChecked: value };
}
return obj;
});
setTechnologies(newState);
const resetFilters = () => {
setTypesOfEmployments([
{ name: "Posao", isChecked: false },
{ name: "Intership", isChecked: false },
]);
setSliderValue([0,0])
setEndingDate("")
setStartingDate("")
};

useEffect(() => {
if(isInitial) {
setIsInitial(false)
return;
}
dispatch(resetIsCheckedValue())
resetFilters();
}, [isTableView])

const updateTypeState = (name, value) => {
const newState = typesOfEmployments.map((obj) => {
if (obj.name === name) {
@@ -57,63 +72,97 @@ const CandidateFilters = ({ open, handleClose,pageSize,currentPage }) => {
setTypesOfEmployments(newState);
};

const handleTechologiesChange = (event) => {
updateState(event.target.name, event.target.checked);
const handleTechologiesChange = (e) => {
const { name } = e.target;

dispatch(changeIsCheckedValue(name));
};

const getSelectedEmploymentType = () => {
return typesOfEmployments.filter((e) => e.isChecked === true);
};

const getSelectedTechnologies = () => {
let k = technologies.filter(t => t.isChecked === true)
let n = []
k.forEach(technology => {
n.push(technology.name)
});

return n;
}

const handleChangeTypeOfEmployment = (name) => {
updateTypeState(name, true);
};

const handleApiResponseSuccess = () => {
handleClose()
handleClose();
};

const isThereSelectedFilter = () => {
let tech = technologies.filter(t => t.isChecked === true);
if(tech.length > 0){
return true;
}
let k = typesOfEmployments.filter(te => te.isChecked === true)
if(k.length > 0){
return true
}

if(sliderValue[0] !== 0 || sliderValue[1] !== 0) {
return true
}
if(startingDate !== "" || endingDate !== ""){
return true;
}

return false
}

const fiterItems = () => {
const tech = technologies
.filter((tech) => tech.isChecked === true)
.map((tech) => tech.name);

const selectedEmploymentType = getSelectedEmploymentType();

isTableView ?

dispatch(
filterCandidates({
pageSize,
currentPage,
minExperience: sliderValue[0],
maxExperience: sliderValue[1],
employmentType: getSelectedEmploymentType()[0].name,
minDateOfApplication:startingDate,
maxDateOfApplication:endingDate,
technologies:getSelectedTechnologies(),
handleApiResponseSuccess
employmentType:selectedEmploymentType.length === 0 ? "" : selectedEmploymentType[0].name,
minDateOfApplication: startingDate,
maxDateOfApplication: endingDate,
technologies: tech,
handleApiResponseSuccess,
})
) :

dispatch(
fetchAdsCandidates({
pageSize,
currentPage,
minExperience: sliderValue[0],
maxExperience: sliderValue[1],
employmentType:selectedEmploymentType.length === 0 ? "" : selectedEmploymentType[0].name,
minDateOfApplication: startingDate,
maxDateOfApplication: endingDate,
technologies: tech,
handleApiResponseSuccess,
})
);
};

const handleChangeStartingDate = (event) => {
console.log(event)
setStartingDate(event.target.value)
}
console.log(event);
setStartingDate(event.target.value);
};

const handleChangeEndingDate = (event) => {
console.log(event)
setEndingDate(event.target.value)
}
console.log(event);
setEndingDate(event.target.value);
};

const getDayBeforeToday = (date = new Date()) => {
const previous = new Date(date.getTime());
previous.setDate(date.getDate() - 1);
return previous;
}
};

const list = () => (
<Box
@@ -200,16 +249,28 @@ const CandidateFilters = ({ open, handleClose,pageSize,currentPage }) => {
<p>Datum prijave</p>
<div className="filter-date-container">
<p>Od</p>
<input type="date" id="start" max={formatDateInput(getDayBeforeToday())} value={startingDate} onChange={handleChangeStartingDate}/>
<input
type="date"
id="start"
max={formatDateInput(getDayBeforeToday())}
value={startingDate}
onChange={handleChangeStartingDate}
/>
</div>
<div className="filter-date-container">
<p>Do</p>
<input type="date" id="start" max={ formatDateInput(new Date())} value={endingDate} onChange={handleChangeEndingDate}/>
<input
type="date"
id="start"
max={formatDateInput(new Date())}
value={endingDate}
onChange={handleChangeEndingDate}
/>
</div>
</div>
</div>
<div className="ad-filters-search" style={{ marginTop: "45px" }}>
<button className="c-btn c-btn--primary" onClick={fiterItems}>
<button className="c-btn c-btn--primary" onClick={fiterItems} disabled={!isThereSelectedFilter()}>
Pretrazi
</button>
</div>
@@ -230,8 +291,10 @@ CandidateFilters.propTypes = {
open: PropType.any,
handleClose: PropType.func,
candidates: PropType.array,
currentPage:PropType.number,
pageSize:PropType.number
currentPage: PropType.number,
pageSize: PropType.number,
isTableView: PropType.bool,
technologies: PropType.array,
};

export default CandidateFilters;

+ 1
- 1
src/constants/keyCodeConstants.js Wyświetl plik

@@ -13,4 +13,4 @@ export const LEFT_ARROW_KEYCODE = 37;
export const BACKSPACE_KEYCODE = 8;
export const TAB_KEYCODE = 9;

export const PAGE_SIZE_CANDIDATES = 6;
export const PAGE_SIZE_CANDIDATES = 2;

+ 41
- 10
src/pages/CandidateDetailsPage/CandidateDetailsPage.js Wyświetl plik

@@ -12,15 +12,17 @@ import { MentionsInput, Mention } from "react-mentions";
import { fetchCandidate } from "../../store/actions/candidate/candidateActions";
import { useMediaQuery } from "@mui/material";
import { useTheme } from "@mui/system";
import CandidateAd from '../../components/Candidates/CandidateAd';
import CandidateAd from "../../components/Candidates/CandidateAd";
import Slider from "react-slick";
import arrow_left from "../../assets/images/arrow_left.png";
import arrow_right from "../../assets/images/arrow_right.png";
import { ADS_PAGE } from "../../constants/pages";
import { ADS_PAGE, CANDIDATES_PAGE } from "../../constants/pages";
import PropTypes from "prop-types";
import { deleteCandidate } from "../../request/candidatesRequest";
import { deleteCandidate } from "../../store/actions/candidate/candidateActions";
import ConfirmDialog from "../../components/MUI/ConfirmDialog";
import deleteIcon from "../../assets/images/delete.png";

const CandidateDetailsPage = ({history}) => {
const CandidateDetailsPage = ({ history }) => {
const dispatch = useDispatch();
const { users } = useSelector((s) => s.users);
const { user } = useSelector((s) => s.user);
@@ -31,6 +33,7 @@ const CandidateDetailsPage = ({history}) => {
const theme = useTheme();
const matches = useMediaQuery(theme.breakpoints.down("680"));
const adsSliderRef = useRef();
const [showDelete, setDelete] = useState(false);

useEffect(() => {
dispatch(fetchCandidate({ id }));
@@ -143,10 +146,6 @@ const CandidateDetailsPage = ({history}) => {
adsSliderRef.current.slickNext();
};

const deleteCurrentCandidate = () => {
dispatch(deleteCandidate({id}))
}

const navigateToDetailsPage = (id) => {
history.push({
pathname: ADS_PAGE + "/" + id,
@@ -154,13 +153,42 @@ const CandidateDetailsPage = ({history}) => {
from: history.location.pathname,
},
});
}
};

const handleApiResponseSuccessDelete = () => {
history.push({
pathname: CANDIDATES_PAGE,
state: {
from: history.location.pathname,
},
});
};

const deleteCandidateHandler = () => {
dispatch(
deleteCandidate({
id,
handleApiResponseSuccess: handleApiResponseSuccessDelete,
})
);
};

return (candidate && Object.keys(candidate).length === 0) ||
user === undefined ? (
<p>Loading...</p>
) : (
<div className="main-candidate-container pl-144 pt-36px">
<ConfirmDialog
open={showDelete}
title={"Brisanje kandidata"}
subtitle={candidate.firstName + " " + candidate.lastName}
imgSrc={deleteIcon}
content="Da li ste sigurni da zelite da obrisete kandidata?"
onClose={() => {
setDelete(false);
}}
onConfirm={deleteCandidateHandler}
/>
<div className="l-t-rectangle"></div>
<div className="r-b-rectangle"></div>
<div className="top-candidate-container">
@@ -172,7 +200,10 @@ const CandidateDetailsPage = ({history}) => {
</p>
</div>
<div className="candidate-option-container">
<IconButton className="c-btn c-btn--primary-outlined candidate-btn" onClick={deleteCurrentCandidate}>
<IconButton
className="c-btn c-btn--primary-outlined candidate-btn"
onClick={() => setDelete(true)}
>
Obrisi
<img src={deleteImage} alt="delete" className="candidates-image" />
</IconButton>

+ 136
- 0
src/pages/CandidatesPage/AdsCandidatesPage.js Wyświetl plik

@@ -0,0 +1,136 @@
import React, { useEffect } from "react";
import arrow_left from "../../assets/images/arrow_left.png";
import arrow_right from "../../assets/images/arrow_right.png";
import adImage from "../../assets/images/.net_icon.png";
import Slider from "react-slick";
import useDynamicRefs from "use-dynamic-refs";
import { fetchAdsCandidates } from "../../store/actions/candidates/candidatesActions";
import { useDispatch, useSelector } from "react-redux";
import CandidateCard from "../../components/Candidates/CandidateCard";

const AdsCandidatesPage = () => {
const dispatch = useDispatch();
const { adsCandidates } = useSelector((s) => s.candidates);
const [getRef, setRef] = useDynamicRefs();

useEffect(() => {
dispatch(
fetchAdsCandidates({
currentPage: 0,
pageSize: 0,
minExperience: 0,
maxExperience: 0,
employmentType: "",
minDateOfApplication: "",
maxDateOfApplication: "",
technologies: [],
})
);
},[dispatch]);

var settings = {
dots: false,
infinite: false,
speed: 400,
slidesToShow: 4,
slidesToScroll: 4,
initialSlide: 0,
arrows: true,
variableWidth: true,
responsive: [
{
breakpoint: 1024,
settings: {
slidesToShow: 3,
slidesToScroll: 3,
infinite: true,
dots: false,
},
},
{
breakpoint: 900,
settings: {
slidesToShow: 2,
slidesToScroll: 2,
initialSlide: 0,
},
},
{
breakpoint: 480,
settings: {
slidesToShow: 1,
slidesToScroll: 1,
},
},
],
};

const getDummyCandidates = (len) => {
const candidates = [];

for (let i = 0; i < 4 - len + 1; i++) {
candidates.push(
<CandidateCard key={i} className="hiddenAd" history={history} />
);
}

return candidates;
};

const activeAdsArrowLeftHandler = (index) => {
getRef(index.toString()).current.slickPrev();
};

const activeAdsArrowRightHandler = (index) => {
getRef(index.toString()).current.slickNext();
};

return (
<div className="ads-candidates-container top-cnd">
{adsCandidates.map((adCandidates, index) => (
<div className="ads-candidates" key={index}>
<div className="ads-candidates-top-container">
<img src={adImage} style={{ width: "49px", height: "39px" }} />
<p className="ads-candidates-title">{adCandidates.title}</p>
<p className="ads-candidates-numberOfApplicants">
| {adCandidates.nubmerOfApplicants} prijavljenih
</p>
</div>
<div
className="ads-candidates-slider"
style={{ display: "flex", marginTop: "31px" }}
>
{adCandidates.applicants.length > 3 && (
<div className="active-ads-ads-arrows">
<button onClick={() => activeAdsArrowLeftHandler(index)}>
<img src={arrow_left} alt="arrow-left" />
</button>
<button onClick={() => activeAdsArrowRightHandler(index)}>
<img src={arrow_right} alt="arrow-right" />
</button>
</div>
)}
<Slider
{...settings}
ref={setRef(index.toString())}
style={{ width: "100%" }}
>
{adCandidates.applicants.map((candidate, index) => (
<CandidateCard
key={index}
candidate={candidate}
history={history}
className={index === 0 ? "" : "left-move-candidateAd"}
/>
))}
{adCandidates.applicants.length <= 4 &&
getDummyCandidates(adCandidates.applicants.length)}
</Slider>
</div>
</div>
))}
</div>
);
};

export default AdsCandidatesPage;

+ 60
- 212
src/pages/CandidatesPage/CandidatesPage.js Wyświetl plik

@@ -1,64 +1,35 @@
import IconButton from "../../components/IconButton/IconButton";
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
fetchAdsCandidates,
filterCandidates,
} from "../../store/actions/candidates/candidatesActions";
import tableImage from "../../assets/images/table.png";
import adImage from "../../assets/images/.net_icon.png";
import filterImage from "../../assets/images/filters.png";
import { formatDate } from "../../util/helpers/dateHelpers";
import planeImage from "../../assets/images/planeVector.png";
import arrow_left from "../../assets/images/arrow_left.png";
import arrow_right from "../../assets/images/arrow_right.png";
import { useMediaQuery } from "@mui/material";
import { useTheme } from "@mui/system";
import { CANDIDATES_PAGE } from "../../constants/pages";
import PropTypes from "prop-types";
import Slider from "react-slick";
import CandidateCard from "../../components/Candidates/CandidateCard";
import useDynamicRefs from "use-dynamic-refs";
import CandidateFilters from "../../components/Candidates/CandidateFilters";
import Pagination from "@mui/material/Pagination";
import { PAGE_SIZE_CANDIDATES } from "../../constants/keyCodeConstants";
import AdsCandidatesPage from "./AdsCandidatesPage";
import TableViewPage from "./TableViewPage";
import { setTechnologiesReq } from "../../store/actions/technologies/technologiesActions";
import { selectTechnologies } from "../../store/selectors/technologiesSelectors";
import { useDispatch, useSelector } from "react-redux";
import InviteDialog from "../../components/MUI/InviteDialog";
import addUser from "../../assets/images/addUser.png";
import x from "../../assets/images/x.png";

const CandidatesPage = ({ history }) => {
const dispatch = useDispatch();
const { candidates } = useSelector((s) => s.candidates);
const { pagination } = useSelector((s) => s.candidates); // pagination is total number of candidates on backend
const { adsCandidates } = useSelector((s) => s.candidates);
const theme = useTheme();
const matches = useMediaQuery(theme.breakpoints.down("sm"));
const [isTableView, setIsTableView] = useState(true);
const [getRef, setRef] = useDynamicRefs();
const [toggleFiltersDrawer, setToggleFiltersDrawer] = useState(false);
const [page, setPage] = React.useState(1);
const technologies = useSelector(selectTechnologies);
const [showInvite, setShowInvite] = useState(false);

useEffect(() => {
dispatch(
filterCandidates({
currentPage: page,
pageSize: PAGE_SIZE_CANDIDATES,
minExperience: 0,
maxExperience: 0,
employmentType: "",
minDateOfApplication: "",
maxDateOfApplication: "",
technologies: [],
})
);
dispatch(fetchAdsCandidates());
}, [dispatch]);

const navigate = (applicantId) => {
history.push({
pathname: CANDIDATES_PAGE + "/" + applicantId,
state: {
from: history.location.pathname,
},
});
};
dispatch(setTechnologiesReq());
}, []);

const changeView = () => {
setIsTableView(!isTableView);
@@ -68,86 +39,54 @@ const CandidatesPage = ({ history }) => {
setToggleFiltersDrawer((oldState) => !oldState);
};

var settings = {
dots: false,
infinite: false,
speed: 400,
slidesToShow: 4,
slidesToScroll: 4,
initialSlide: 0,
arrows: true,
variableWidth: true,
responsive: [
{
breakpoint: 1024,
settings: {
slidesToShow: 3,
slidesToScroll: 3,
infinite: true,
dots: false,
},
},
{
breakpoint: 900,
settings: {
slidesToShow: 2,
slidesToScroll: 2,
initialSlide: 0,
},
},
{
breakpoint: 480,
settings: {
slidesToShow: 1,
slidesToScroll: 1,
},
},
],
};

const getDummyCandidates = (len) => {
const candidates = [];

for (let i = 0; i < 4 - len + 1; i++) {
candidates.push(
<CandidateCard key={i} className="hiddenAd" history={history} />
);
}

return candidates;
};

const activeAdsArrowLeftHandler = (index) => {
getRef(index.toString()).current.slickPrev();
};

const activeAdsArrowRightHandler = (index) => {
getRef(index.toString()).current.slickNext();
};

const handleChange = (event, value) => {
dispatch(
filterCandidates({
currentPage: value,
pageSize: PAGE_SIZE_CANDIDATES,
minExperience: 0,
maxExperience: 5,
employmentType: "Intership",
minDateOfApplication: "10/05/2020",
maxDateOfApplication: "10/10/2022",
technologies: [],
})
);
setPage(value);
const inviteCandidate = () => {
setShowInvite(true)
};

return (
<div className="main-candidates-container pl-144 pt-36px">
<InviteDialog
open={showInvite}
onClose={() => {
setShowInvite(false);
}}
title={
<div
className="flex-center"
style={{ justifyContent: "space-between" }}
>
<div className="flex-center" style={{ justifyContent: "start" }}>
<img
style={{
position: "relative",
top: -0.25,
paddingRight: "10px",
}}
src={addUser}
/>
<h5>Invite korisnika</h5>
<div className="vr"></div>
<p className="dialog-subtitle">Registration link</p>
</div>
<IconButton onClick={() => setShowInvite(false)}>
<img
style={{
position: "relative",
top: -0.25,
}}
src={x}
/>
</IconButton>
</div>
}
/>
<CandidateFilters
open={toggleFiltersDrawer}
handleClose={handleToggleFiltersDrawer}
pageSize={PAGE_SIZE_CANDIDATES}
currentPage={page}
isTableView={isTableView}
technologies={technologies}
/>
<div className="l-t-rectangle"></div>
<div className="r-b-rectangle"></div>
@@ -222,108 +161,17 @@ const CandidatesPage = ({ history }) => {
</div>
</div>
{isTableView ? (
<div className="candidates-table">
<table
className="usersTable"
style={{ width: "1117px", height: "100px" }}
>
<thead>
<tr className="headingRow">
<th>Ime i prezime</th>
<th>Iskustvo</th>
<th>Datum prijave</th>
<th>Pozicija</th>
<th>CV link</th>
</tr>
</thead>
<tbody>
{candidates.map((candidate, index) => (
<tr
key={index}
className="secondaryRow cadidate-row"
style={{
width: "800px",
height: "40px",
borderRadius: "12px",
cursor: "pointer",
}}
onClick={() => navigate(candidate.applicantId)}
>
<td>
{candidate.firstName} {candidate.lastName}
</td>
<td>{candidate.experience}</td>
<td>{formatDate(candidate.dateOfApplication)}</td>
<td>{candidate.position}</td>
<td>
<a href={candidate.CV} className="cvLink">
{candidate.firstName}
{candidate.lastName}.pdf
</a>
</td>
</tr>
))}
</tbody>
</table>
<Pagination
count={
parseInt(pagination) <= PAGE_SIZE_CANDIDATES
? 1
: Math.ceil(parseInt(pagination) / PAGE_SIZE_CANDIDATES)
}
color="primary"
style={{ alignSelf: "center", marginTop: "20px" }}
onChange={handleChange}
shape="rounded"
/>
</div>
<TableViewPage history={history} page={page} setPage={setPage} />
) : (
<div className="ads-candidates-container top-cnd">
{adsCandidates.map((adCandidates, index) => (
<div className="ads-candidates" key={index}>
<div className="ads-candidates-top-container">
<img src={adImage} style={{ width: "49px", height: "39px" }} />
<p className="ads-candidates-title">{adCandidates.title}</p>
<p className="ads-candidates-numberOfApplicants">
| {adCandidates.nubmerOfApplicants} prijavljenih
</p>
</div>
<div
className="ads-candidates-slider"
style={{ display: "flex", marginTop: "31px" }}
>
{adCandidates.applicants.length > 3 && (
<div className="active-ads-ads-arrows">
<button onClick={() => activeAdsArrowLeftHandler(index)}>
<img src={arrow_left} alt="arrow-left" />
</button>
<button onClick={() => activeAdsArrowRightHandler(index)}>
<img src={arrow_right} alt="arrow-right" />
</button>
</div>
)}
<Slider
{...settings}
ref={setRef(index.toString())}
style={{ width: "100%" }}
>
{adCandidates.applicants.map((candidate, index) => (
<CandidateCard
key={index}
candidate={candidate}
history={history}
className={index === 0 ? "" : "left-move-candidateAd"}
/>
))}
{adCandidates.applicants.length <= 4 &&
getDummyCandidates(adCandidates.applicants.length)}
</Slider>
</div>
</div>
))}
</div>
<AdsCandidatesPage/>
)}
<IconButton className={'c-btn candidate-btn invite-btn invite-btn-color ' + (isTableView === false ? 'proba' : '')}>
<IconButton
className={
"c-btn candidate-btn invite-btn invite-btn-color " +
(isTableView === false ? "proba" : "")
}
onClick={inviteCandidate}
>
<img src={planeImage} alt="table" className="candidates-image" />
Invite
</IconButton>

+ 126
- 0
src/pages/CandidatesPage/TableViewPage.js Wyświetl plik

@@ -0,0 +1,126 @@
import React, { useEffect } from "react";
import Pagination from "@mui/material/Pagination";
import { formatDate } from "../../util/helpers/dateHelpers";
import { useDispatch, useSelector } from "react-redux";
import PropTypes from "prop-types";
import { CANDIDATES_PAGE } from "../../constants/pages";
import { filterCandidates } from "../../store/actions/candidates/candidatesActions";
import { PAGE_SIZE_CANDIDATES } from "../../constants/keyCodeConstants";

const TableViewPage = ({ history, setPage, page }) => {
const dispatch = useDispatch();
const { candidates } = useSelector((s) => s.candidates);
const { pagination } = useSelector((s) => s.candidates); // pagination is total number of candidates on backend

const navigate = (applicantId) => {
history.push({
pathname: CANDIDATES_PAGE + "/" + applicantId,
state: {
from: history.location.pathname,
},
});
};

useEffect(() => {
dispatch(
filterCandidates({
currentPage: page,
pageSize: PAGE_SIZE_CANDIDATES,
minExperience: 0,
maxExperience: 0,
employmentType: "",
minDateOfApplication: "",
maxDateOfApplication: "",
technologies: [],
})
);
}, [dispatch]);

const handleChange = (_, value) => {
dispatch(
filterCandidates({
currentPage: value,
pageSize: PAGE_SIZE_CANDIDATES,
minExperience: 0,
maxExperience: 0,
employmentType: "",
minDateOfApplication: "",
maxDateOfApplication: "",
technologies: [],
})
);
setPage(value);
};

return (
<div className="candidates-table">
<table
className="usersTable"
style={{ width: "1117px", height: "100px" }}
>
<thead>
<tr className="headingRow">
<th>Ime i prezime</th>
<th>Iskustvo</th>
<th>Datum prijave</th>
<th>Pozicija</th>
<th>CV link</th>
</tr>
</thead>
<tbody>
{candidates.map((candidate, index) => (
<tr
key={index}
className="secondaryRow cadidate-row"
style={{
width: "800px",
height: "40px",
borderRadius: "12px",
cursor: "pointer",
}}
onClick={() => navigate(candidate.applicantId)}
>
<td>
{candidate.firstName} {candidate.lastName}
</td>
<td>{candidate.experience}</td>
<td>{formatDate(candidate.dateOfApplication)}</td>
<td>{candidate.position}</td>
<td>
<a href={candidate.CV} className="cvLink">
{candidate.firstName}
{candidate.lastName}.pdf
</a>
</td>
</tr>
))}
</tbody>
</table>
<Pagination
count={
parseInt(pagination) <= PAGE_SIZE_CANDIDATES
? 1
: Math.ceil(parseInt(pagination) / PAGE_SIZE_CANDIDATES)
}
color="primary"
style={{ alignSelf: "center", marginTop: "20px" }}
onChange={handleChange}
shape="rounded"
/>
</div>
);
};

TableViewPage.propTypes = {
history: PropTypes.shape({
replace: PropTypes.func,
push: PropTypes.func,
location: PropTypes.shape({
pathname: PropTypes.string,
}),
}),
setPage: PropTypes.func,
page: PropTypes.number,
};

export default TableViewPage;

+ 1
- 1
src/request/apiEndpoints.js Wyświetl plik

@@ -17,7 +17,7 @@ export default {
},
candidates: {
filteredCandidates:base + "/applicants",
allAdsCandidates: base + "/applicants/adsApplicants"
allFilteredAdsCandidates: base + "/applicants/adsApplicants"
},
ads: {
allAds: base + "/ads",

+ 26
- 3
src/request/candidatesRequest.js Wyświetl plik

@@ -30,7 +30,30 @@ export const getCandidate = (id) =>
getRequest(apiEndpoints.candidates.filteredCandidates + "/" + id);
export const createComment = (data) =>
postRequest(apiEndpoints.comments.addComment, data);
export const getAllAdsCandidates = () =>
getRequest(apiEndpoints.candidates.allAdsCandidates);
export const getFilteredAdsCandidates = (payload) => {
let technologiesQuery = "";
for (let i = 0; i < payload.technologies.length; i++) {
technologiesQuery += `technologies=${payload.technologies[i]}&`;
}
return getRequest(
apiEndpoints.candidates.allFilteredAdsCandidates +
"?currentPage=" +
payload.currentPage +
"&pageSize=" +
payload.pageSize +
"&minExperience=" +
payload.minExperience +
"&maxExperience=" +
payload.maxExperience +
"&employmentType=" +
payload.employmentType +
"&minDateOfApplication=" +
payload.minDateOfApplication +
"&maxDateOfApplication=" +
payload.maxDateOfApplication +
"&" +
technologiesQuery
);
}
export const deleteCandidate = (id) =>
deleteRequest(apiEndpoints.candidates.allCandidates + "?id=" + id);
deleteRequest(apiEndpoints.candidates.filteredCandidates + "?id=" + id);

+ 3
- 2
src/store/actions/candidates/candidatesActions.js Wyświetl plik

@@ -22,8 +22,9 @@ import {
payload,
});

export const fetchAdsCandidates = () => ({
export const fetchAdsCandidates = (payload) => ({
type: ADS_CANDIDATES_FETCH,
payload
});

export const fetchAdsCandidatesError = (payload) => ({
@@ -34,4 +35,4 @@ import {
export const fetchAdsCandidatesSuccess = (payload) => ({
type: ADS_CANDIDATES_SUCCESS,
payload,
});
});

+ 1
- 0
src/store/actions/technologies/technologiesActionConstants.js Wyświetl plik

@@ -3,3 +3,4 @@ export const FETCH_TECHNOLOGIES_ERR = "FETCH_TECHNOLOGIES_ERR";
export const FETCH_TECHNOLOGIES_SUCCESS = "FETCH_TECHNOLOGIES_SUCCESS";

export const CHANGE_ISCHECKED_VALUE = "CHANGE_ISCHECKED_VALUE";
export const RESET_IS_CHECKED_VALUE = "RESET_IS_CHECKED_VALUE"

+ 7
- 2
src/store/actions/technologies/technologiesActions.js Wyświetl plik

@@ -2,7 +2,8 @@ import {
FETCH_TECHNOLOGIES_ERR,
FETCH_TECHNOLOGIES_SUCCESS,
FETCH_TECHNOLOGIES_REQ,
CHANGE_ISCHECKED_VALUE
CHANGE_ISCHECKED_VALUE,
RESET_IS_CHECKED_VALUE
} from "./technologiesActionConstants";
export const setTechnologiesReq = () => ({
@@ -22,4 +23,8 @@ import {
export const changeIsCheckedValue = (payload) => ({
type: CHANGE_ISCHECKED_VALUE,
payload,
});
});

export const resetIsCheckedValue = () => ({
type:RESET_IS_CHECKED_VALUE
})

+ 2
- 2
src/store/reducers/candidates/candidatesReducer.js Wyświetl plik

@@ -10,7 +10,7 @@ const initialState = {
candidates: [],
adsCandidates: [],
errorMessage: "",
pagination: 0,
pagination: 0
};

export default createReducer(
@@ -50,4 +50,4 @@ function setAdsCandidatesError(state, action) {
...state,
errorMessage: action.payload,
};
}
}

+ 6
- 0
src/store/reducers/technology/technologiesReducer.js Wyświetl plik

@@ -3,6 +3,7 @@ import {
FETCH_TECHNOLOGIES_SUCCESS,
FETCH_TECHNOLOGIES_ERR,
CHANGE_ISCHECKED_VALUE,
RESET_IS_CHECKED_VALUE
} from "../../actions/technologies/technologiesActionConstants";

const initialState = {
@@ -15,6 +16,7 @@ export default createReducer(
[FETCH_TECHNOLOGIES_SUCCESS]: setStateTechnologies,
[FETCH_TECHNOLOGIES_ERR]: setStateErrorMessage,
[CHANGE_ISCHECKED_VALUE]: setIsCheckedTechnology,
[RESET_IS_CHECKED_VALUE]: resetIsCheckedTechnologies
},
initialState
);
@@ -43,3 +45,7 @@ function setIsCheckedTechnology(state, action) {
),
};
}

function resetIsCheckedTechnologies(state){
return {...state,technologies:state.technologies.map(t => ({...t,isChecked:false}))}
}

+ 12
- 8
src/store/saga/candidatesSaga.js Wyświetl plik

@@ -4,20 +4,20 @@ import { addHeaderToken } from "../../request";
import {
createComment,
getCandidate,
getAllAdsCandidates,
getFilteredAdsCandidates,
deleteCandidate,
getFilteredCandidates
} from "../../request/candidatesRequest";
import { authScopeStringGetHelper } from "../../util/helpers/authScopeHelpers";
import {
ADS_CANDIDATES_FETCH,
FILTER_CANDIDATES
FILTER_CANDIDATES,
} from "../actions/candidates/candidatesActionConstants";
import {
fetchAdsCandidatesError,
fetchAdsCandidatesSuccess,
filterCandidatesSuccess,
filterCandidatesError,
filterCandidatesError
} from "../actions/candidates/candidatesActions";
import { rejectErrorCodeHelper } from "../../util/helpers/rejectErrorCodeHelper";
import {
@@ -33,8 +33,6 @@ import {
CANDIDATE_COMMENTS_FETCH,
DELETE_CANDIDATE,
} from "../actions/candidate/candidateActionConstants";
import { CANDIDATES_PAGE } from "../../constants/pages";
import history from "../utils/history";


export function* filterCandidates({ payload }) {
@@ -77,12 +75,15 @@ export function* addComment(data) {
}
}

export function* getAdsCandidates() {
export function* getAdsCandidates({payload}) {
try {
const JwtToken = yield call(authScopeStringGetHelper, JWT_TOKEN);
yield call(addHeaderToken, JwtToken);
const { data } = yield call(getAllAdsCandidates);
const { data } = yield call(getFilteredAdsCandidates,payload);
yield put(fetchAdsCandidatesSuccess(data));
if(payload.handleApiResponseSuccess){
yield call(payload.handleApiResponseSuccess)
}
} catch (error) {
const errorMessage = yield call(rejectErrorCodeHelper, error);
yield put(fetchAdsCandidatesError(errorMessage));
@@ -90,9 +91,12 @@ export function* getAdsCandidates() {
}

export function* deleteSingleCandidate({ payload }) {
console.log(payload)
try {
yield call(deleteCandidate, payload.id);
yield call(history.replace, CANDIDATES_PAGE);
if(payload.handleApiResponseSuccess){
yield call(payload.handleApiResponseSuccess)
}
yield put(deleteCandidateSuccess());
} catch (error) {
const errorMessage = yield call(rejectErrorCodeHelper, error);

Ładowanie…
Anuluj
Zapisz