Parcourir la source

add new filters

pull/100/head
Dzenis Hadzifejzovic il y a 3 ans
Parent
révision
39f25bbb7d

BIN
src/assets/images/search.png Voir le fichier


+ 1
- 0
src/assets/styles/components/_candidate-card.scss Voir le fichier

border-radius: 18px; border-radius: 18px;
width: 354px; width: 354px;
height: 238px; height: 238px;
margin-right: 27px;
} }


@media only screen and (max-width: 480px) { @media only screen and (max-width: 480px) {

+ 26
- 27
src/assets/styles/components/_candidatePage.scss Voir le fichier

.main-candidate-container { .main-candidate-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-top: 36px;
padding-right: 72px;
} }


.top-candidate-container { .top-candidate-container {
display: flex; display: flex;
width: 100%;
justify-content: space-between; justify-content: space-between;
margin-left: 144px;
} }


.candidate-header { .candidate-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin-top: 14px; margin-top: 14px;
margin-left: 144px;
} }


.technologies-candidate-container { .technologies-candidate-container {
border-radius: 12px; border-radius: 12px;
width: 247px; width: 247px;
height: 238px; height: 238px;
margin-left: 20px;
cursor: pointer; cursor: pointer;
margin-left: 27px;
} }


.applicant-add-date { .applicant-add-date {
color: #272727; color: #272727;
} }


.proba {
.applicant-ads-container-2 {
display: flex; display: flex;
align-items: center; align-items: center;
margin-top: 18px; margin-top: 18px;
} }


@media only screen and (max-width: 361px) { @media only screen and (max-width: 361px) {
.main-candidate-container{
padding-right: 36px;
}
.top-candidate-container {
margin-left: 36px;
}

.content-candidate-container {
margin-left: 36px;
}

.comment-input { .comment-input {
@extend .tagStyle; @extend .tagStyle;
min-width: 195px; min-width: 195px;
justify-content: initial; justify-content: initial;
} }


.proba {
.applicant-ads-container-2 {
flex-direction: column-reverse; flex-direction: column-reverse;
align-items: flex-start; align-items: flex-start;
} }


.applicant-ads-back-button { .applicant-ads-back-button {
font-size: 14px; font-size: 14px;
line-height: 16px;
letter-spacing: 0em;
line-height: 18px;
margin-right: 0;
} }

.applicant-cv-button { .applicant-cv-button {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
padding: 18px 72px;
gap: 10px;
background: #226cb0;
border-radius: 9px;
width: 212px;
height: 51px;
font-family: "Source Sans Pro";
font-weight: 600;
padding: 18px 45px;
width: 158px;
font-size: 12px; font-size: 12px;
line-height: 15px; line-height: 15px;
letter-spacing: 0.04em; letter-spacing: 0.04em;
text-transform: uppercase;
color: #ffffff;
} }


.applicant-cv-button {
padding: 18px 45px;
width: 158px;
height: 51px;
.active-ads-ads-arrows {
margin-left: -0.75rem;
} }


.active-ads-ads-arrows{
margin-left: -0.75rem;
.applicant-ads-buttons-container{
margin-left: 36px;
} }
} }

+ 68
- 2
src/assets/styles/components/_candidatesPage.scss Voir le fichier

display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-bottom: 36px; margin-bottom: 36px;
margin-top: 36px;
padding-left: 72px;
} }


.top-candidates-container { .top-candidates-container {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin-left: 72px;
margin-right: 72px;
} }


.candidate-btn { .candidate-btn {
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
font-size: 16px; font-size: 16px;
line-height: 20px;
color: #226cb0; color: #226cb0;
text-transform: none; text-transform: none;
} }


.ads-candidates-slider { .ads-candidates-slider {
display: flex; display: flex;
margin-top: 31px;
}

.ads-candidates-slider .slick-track{
margin: 0 !important;
}

.ads-candidates-slider .slick-slider{
width: 100% !important;
} }


.ads-candidates-top-container { .ads-candidates-top-container {
display: flex; display: flex;
align-items: center; align-items: center;
margin-left: 72px;
} }


.ads-candidates-title { .ads-candidates-title {
margin-left: -0.75rem !important; margin-left: -0.75rem !important;
} }


.left-move-candidateAd-page {
margin-left: 16px;
}

.left-move-candidateAd-page-2 {
margin-left: 42px;
}

.candidate-search-field{
width: 816px;
border:1px solid #226CB0;
border-radius:10px;
padding: 20px;
background-color: white;
position: absolute;
top: 112px;
left: 800px;
z-index: 1000;
}

.candidate-search-field::placeholder{
font-size: 1rem;
color: #9D9D9D;
font-style: italic;
}

@media only screen and (max-width: 600px) { @media only screen and (max-width: 600px) {
.ads-candidates-title { .ads-candidates-title {
font-size: 18px; font-size: 18px;
.ads-candidates-numberOfApplicants { .ads-candidates-numberOfApplicants {
font-size: 14px; font-size: 14px;
} }
}
}

@media only screen and (max-width: 361px) {
.ads-candidates-slider {
flex-direction: column-reverse;
}

.main-candidates-container {
padding-left: 0px;
}

.top-candidates-container {
margin-left: 36px;
margin-right: 29px;
}
.candidates-textField{
margin-left:36px
}

.ads-candidates-top-container{
margin-left: 36px;
}

.left-move-candidateAd-page{
margin-left: -12px;
}
}

+ 2
- 1
src/components/IconButton/IconButton.js Voir le fichier

const IconButton = ({ children, onClick, className }) => { const IconButton = ({ children, onClick, className }) => {
const buttonRef = useRef(null); const buttonRef = useRef(null);


function handleClick() {
function handleClick(e) {
e.stopPropagation()
buttonRef.current.blur(); buttonRef.current.blur();
if (typeof onClick === 'function') { if (typeof onClick === 'function') {
onClick(); onClick();

+ 30
- 19
src/pages/CandidatesPage/AdsCandidatesPage.js Voir le fichier

import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import CandidateCard from "../../components/Candidates/CandidateCard"; import CandidateCard from "../../components/Candidates/CandidateCard";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { useTheme } from "@mui/system";
import { useMediaQuery } from "@mui/material";


const AdsCandidatesPage = ({history}) => {
const AdsCandidatesPage = ({ history, search }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { adsCandidates } = useSelector((s) => s.candidates); const { adsCandidates } = useSelector((s) => s.candidates);
const [getRef, setRef] = useDynamicRefs(); const [getRef, setRef] = useDynamicRefs();
const theme = useTheme();
const matches = useMediaQuery(theme.breakpoints.down("361"));


useEffect(() => { useEffect(() => {
dispatch( dispatch(
technologies: [], technologies: [],
}) })
); );
},[dispatch]);
}, [dispatch]);


var settings = { var settings = {
dots: false, dots: false,
infinite: false, infinite: false,
speed: 400, speed: 400,
slidesToShow: 4,
slidesToScroll: 4,
slidesToShow: 3,
slidesToScroll: 3,
initialSlide: 0, initialSlide: 0,
arrows: true, arrows: true,
variableWidth: true, variableWidth: true,
{ {
breakpoint: 1024, breakpoint: 1024,
settings: { settings: {
slidesToShow: 3,
slidesToScroll: 3,
slidesToShow: 2,
slidesToScroll: 2,
infinite: true, infinite: true,
dots: false, dots: false,
}, },
{ {
breakpoint: 900, breakpoint: 900,
settings: { settings: {
slidesToShow: 2,
slidesToScroll: 2,
slidesToShow: 1,
slidesToScroll: 1,
initialSlide: 0, initialSlide: 0,
}, },
}, },
settings: { settings: {
slidesToShow: 1, slidesToShow: 1,
slidesToScroll: 1, slidesToScroll: 1,
initialSlide:0
}, },
}, },
], ],
</div> </div>
<div <div
className="ads-candidates-slider" className="ads-candidates-slider"
style={{ display: "flex", marginTop: "31px" }}
> >
{adCandidates.applicants.length > 3 && ( {adCandidates.applicants.length > 3 && (
<div className="active-ads-ads-arrows">
<div className="active-ads-ads-arrows" style={matches ? {marginLeft:36} : {marginLeft:0}}>
<button onClick={() => activeAdsArrowLeftHandler(index)}> <button onClick={() => activeAdsArrowLeftHandler(index)}>
<img src={arrow_left} alt="arrow-left" /> <img src={arrow_left} alt="arrow-left" />
</button> </button>
<Slider <Slider
{...settings} {...settings}
ref={setRef(index.toString())} ref={setRef(index.toString())}
style={{ width: "100%" }}
style={{ width: "100%",marginLeft:adCandidates.applicants.length > 3 ? (matches ? 30 : 20) : (matches ? 30 : 66) }}
className='left-move-candidateAd-page'
> >
{adCandidates.applicants.map((candidate, index) => (
<CandidateCard
key={index}
candidate={candidate}
history={history}
className={index === 0 ? "" : "left-move-candidateAd"}
/>
))}
{adCandidates.applicants
.filter((candidate) =>
(candidate.firstName + " " + candidate.lastName)
.toLowerCase()
.includes(search.toLowerCase())
)
.map((candidate, index) => (
<CandidateCard
key={index}
candidate={candidate}
history={history}
/>
))}
{adCandidates.applicants.length <= 4 && {adCandidates.applicants.length <= 4 &&
getDummyCandidates(adCandidates.applicants.length)} getDummyCandidates(adCandidates.applicants.length)}
</Slider> </Slider>
pathname: PropTypes.string, pathname: PropTypes.string,
}), }),
}), }),
search: PropTypes.string,
}; };


export default AdsCandidatesPage; export default AdsCandidatesPage;

+ 6
- 7
src/pages/CandidatesPage/CandidateDetailsPage.js Voir le fichier

slidesToShow: 5, slidesToShow: 5,
slidesToScroll: 5, slidesToScroll: 5,
initialSlide: 0, initialSlide: 0,
arrows: true,
arrows: false,
variableWidth: true, variableWidth: true,
responsive: [ responsive: [
{ {
user === undefined ? ( user === undefined ? (
<p>Loading...</p> <p>Loading...</p>
) : ( ) : (
<div className="main-candidate-container pl-144 pt-36px">
<div className="main-candidate-container">
<ConfirmDialog <ConfirmDialog
open={showDelete} open={showDelete}
title={"Brisanje kandidata"} title={"Brisanje kandidata"}
</div> </div>
</div> </div>
<div className="applicant-ads-container"> <div className="applicant-ads-container">
<p>Sve prijave</p>
<div className="proba">
<div>
<p style={{marginLeft:matches ? 36 : 144}}>Sve prijave</p>
<div className="applicant-ads-container-2">
<div style={{marginLeft:matches ? 36 : (candidate.ads.length < 5 ? 108 : 72)}}>
{(matches ? candidate.ads.length > 1 : candidate.ads.length > 5) && ( {(matches ? candidate.ads.length > 1 : candidate.ads.length > 5) && (
<div className="active-ads-ads-arrows"> <div className="active-ads-ads-arrows">
<button onClick={activeAdsArrowLeftHandler}> <button onClick={activeAdsArrowLeftHandler}>
</div> </div>
)} )}
</div> </div>
<Slider {...settings} style={{ width: "100%" }} ref={adsSliderRef}>
<Slider {...settings} style={{ width: "100%"}} ref={adsSliderRef}>
{candidate.ads.map((add, index) => ( {candidate.ads.map((add, index) => (
<CandidateAd <CandidateAd
add={add} add={add}
key={index} key={index}
onclick={() => navigateToDetailsPage(add.id)} onclick={() => navigateToDetailsPage(add.id)}
className={(matches === true || candidate.ads.length <= 5) && index === 0 ? "left-move-candidateAd" : "" }
/> />
))} ))}
{candidate.ads.length <= 5 && getDummyAds(candidate.ads.length)} {candidate.ads.length <= 5 && getDummyAds(candidate.ads.length)}

+ 114
- 50
src/pages/CandidatesPage/CandidatesPage.js Voir le fichier

import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import tableImage from "../../assets/images/table.png"; import tableImage from "../../assets/images/table.png";
import filterImage from "../../assets/images/filters.png"; import filterImage from "../../assets/images/filters.png";
import searchImage from "../../assets/images/search.png";
import { useMediaQuery } from "@mui/material"; import { useMediaQuery } from "@mui/material";
import { useTheme } from "@mui/system"; import { useTheme } from "@mui/system";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { setTechnologiesReq } from "../../store/actions/technologies/technologiesActions"; import { setTechnologiesReq } from "../../store/actions/technologies/technologiesActions";
import { selectTechnologies } from "../../store/selectors/technologiesSelectors"; import { selectTechnologies } from "../../store/selectors/technologiesSelectors";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import Fade from "@mui/material/Fade";


const CandidatesPage = ({ history }) => { const CandidatesPage = ({ history }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const [toggleFiltersDrawer, setToggleFiltersDrawer] = useState(false); const [toggleFiltersDrawer, setToggleFiltersDrawer] = useState(false);
const [page, setPage] = React.useState(1); const [page, setPage] = React.useState(1);
const technologies = useSelector(selectTechnologies); const technologies = useSelector(selectTechnologies);
const [search, setSearch] = useState("");
const [isSearchFieldVisible, setIsSearchFieldVisible] = useState(false);


useEffect(() => { useEffect(() => {
dispatch(setTechnologiesReq()); dispatch(setTechnologiesReq());
}, []); }, []);


const changeView = () => { const changeView = () => {
setSearch("");
setIsTableView(!isTableView); setIsTableView(!isTableView);
}; };


setToggleFiltersDrawer((oldState) => !oldState); setToggleFiltersDrawer((oldState) => !oldState);
}; };


const handleChangeVisibility = () => {
setIsSearchFieldVisible(!isSearchFieldVisible);
};

const stopPropagation = (e) => {
e.stopPropagation();
};

const input = (
<div>
<input
placeholder="Pretrazi..."
value={search}
onChange={(e) => setSearch(e.target.value)}
className="candidate-search-field"
onClick={stopPropagation}
style={{ zIndex: 1000 }}
/>
</div>
);

return ( return (
<div className="main-candidates-container pl-144 pt-36px">
<div
className="main-candidates-container"
onClick={() => setIsSearchFieldVisible(false)}
>
<CandidateFilters <CandidateFilters
open={toggleFiltersDrawer} open={toggleFiltersDrawer}
handleClose={handleToggleFiltersDrawer} handleClose={handleToggleFiltersDrawer}
Kandidati Kandidati
</p> </p>
)} )}
<div className="candidates-options-container">
{!matches ? (
isTableView ? (
<div style={{ postion: "relative" }}>
<Fade in={isSearchFieldVisible} timeout={500}>
{input}
</Fade>
<Fade in={isSearchFieldVisible} timeout={500}>
<div
style={{
position: "absolute",
zIndex: 10000,
marginLeft: 300,
marginTop: 15,
}}
>
<img src={searchImage} />
</div>
</Fade>
<div className="candidates-options-container">
{!matches ? (
isTableView ? (
<IconButton
className="c-btn c-btn--primary-outlined candidate-btn all-white-btn"
onClick={changeView}
>
Tablicni prikaz
<img
src={tableImage}
alt="table"
className="candidates-image"
/>
</IconButton>
) : (
<IconButton
className="c-btn c-btn--primary-outlined candidate-btn"
onClick={changeView}
>
Tablicni prikaz
<img
src={tableImage}
alt="table"
className="candidates-image"
/>
</IconButton>
)
) : (
<IconButton <IconButton
className="c-btn c-btn--primary-outlined candidate-btn all-white-btn"
className="c-btn--primary-outlined c-btn candidate-btn-mobile"
onClick={changeView} onClick={changeView}
> >
Tablicni prikaz
<img <img
className="candidate-image-mobile"
src={tableImage} src={tableImage}
alt="table" alt="table"
/>
</IconButton>
)}
{!matches && (
<IconButton
className="c-btn c-btn--primary-outlined candidate-btn"
onClick={handleChangeVisibility}
>
Pretraga
<img
src={searchImage}
alt="filter"
className="candidates-image" className="candidates-image"
/> />
</IconButton> </IconButton>
) : (
)}
{!matches ? (
<IconButton <IconButton
className="c-btn c-btn--primary-outlined candidate-btn" className="c-btn c-btn--primary-outlined candidate-btn"
onClick={changeView}
onClick={handleToggleFiltersDrawer}
> >
Tablicni prikaz
Filteri
<img <img
src={tableImage}
alt="table"
src={filterImage}
alt="filter"
className="candidates-image" className="candidates-image"
/> />
</IconButton> </IconButton>
)
) : (
<IconButton
className="c-btn--primary-outlined c-btn candidate-btn-mobile"
onClick={changeView}
>
<img
className="candidate-image-mobile"
src={tableImage}
alt="table"
/>
</IconButton>
)}
{!matches ? (
<IconButton
className="c-btn c-btn--primary-outlined candidate-btn"
onClick={handleToggleFiltersDrawer}
>
Filteri
<img
src={filterImage}
alt="filter"
className="candidates-image"
/>
</IconButton>
) : (
<IconButton
className="c-btn c-btn--primary-outlined candidate-btn-mobile"
onClick={handleToggleFiltersDrawer}
>
<img
src={filterImage}
alt="filter"
className="candidate-image-mobile"
/>
</IconButton>
)}
) : (
<IconButton
className="c-btn c-btn--primary-outlined candidate-btn-mobile"
onClick={handleToggleFiltersDrawer}
>
<img
src={filterImage}
alt="filter"
className="candidate-image-mobile"
/>
</IconButton>
)}
</div>
</div> </div>
</div> </div>
{isTableView ? ( {isTableView ? (
<TableViewPage history={history} page={page} setPage={setPage} />
<TableViewPage
history={history}
page={page}
setPage={setPage}
search={search}
/>
) : ( ) : (
<AdsCandidatesPage history={history}/>
<AdsCandidatesPage history={history} search={search} />
)} )}
</div> </div>
); );

+ 66
- 35
src/pages/CandidatesPage/TableViewPage.js Voir le fichier

import { CANDIDATES_PAGE } from "../../constants/pages"; import { CANDIDATES_PAGE } from "../../constants/pages";
import { filterCandidates } from "../../store/actions/candidates/candidatesActions"; import { filterCandidates } from "../../store/actions/candidates/candidatesActions";
import { PAGE_SIZE_CANDIDATES } from "../../constants/keyCodeConstants"; import { PAGE_SIZE_CANDIDATES } from "../../constants/keyCodeConstants";
import { useTheme } from "@mui/system";
import { useMediaQuery } from "@mui/material";


const TableViewPage = ({ history, setPage, page }) => {
const TableViewPage = ({ history, setPage, page, search }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { candidates } = useSelector((s) => s.candidates); const { candidates } = useSelector((s) => s.candidates);
const { pagination } = useSelector((s) => s.candidates); // pagination is total number of candidates on backend const { pagination } = useSelector((s) => s.candidates); // pagination is total number of candidates on backend
const theme = useTheme();
const matches = useMediaQuery(theme.breakpoints.down("361"));


const navigate = (applicantId) => { const navigate = (applicantId) => {
history.push({ history.push({
setPage(value); setPage(value);
}; };


const formatLabel = (string, value) => {
if (!value) {
return string;
}
return (
<span>
{string.split(value).reduce((prev, current, i) => {
if (!i) {
return [current];
}
return prev.concat(
<b className="highlighted" key={value + current}>
{value}
</b>,
current
);
}, [])}
</span>
);
};

return ( return (
<div
className="candidates-table"
>
<div style={{overflowX:'auto'}}>
<table
className="usersTable"
style={{ width: "1017px" }}
>
<div className="candidates-table">
<div style={{ overflowX: "auto",marginLeft:matches ? 36 : 72 }}>
<table className="usersTable" style={{ width: "1017px" }}>
<thead> <thead>
<tr className="headingRow"> <tr className="headingRow">
<th>Ime i prezime</th> <th>Ime i prezime</th>
</tr> </tr>
</thead> </thead>
<tbody> <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>
))}
{candidates
.filter((n) =>
(n.firstName + " " + n.lastName)
.toLowerCase()
.includes(search.toLowerCase())
)
.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).includes(search) ? (
formatLabel(candidate.firstName + " " + candidate.lastName, search)
) : (
<span>{candidate.firstName + " " + candidate.lastName}</span>
)}
</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> </tbody>
</table> </table>
</div> </div>
}), }),
setPage: PropTypes.func, setPage: PropTypes.func,
page: PropTypes.number, page: PropTypes.number,
search: PropTypes.string,
}; };


export default TableViewPage; export default TableViewPage;

Chargement…
Annuler
Enregistrer