瀏覽代碼

Merge branch 'feature/selection_processes_FE' of Neca/HRCenter into FE_dev

pull/44/head
safet.purkovic 3 年之前
父節點
當前提交
9e90da1544

+ 7
- 1
src/AppRoutes.js 查看文件

@@ -14,7 +14,9 @@ import {
USERS_PAGE,
CANDIDATES_PAGE,
USER_DETAILS_PAGE,
CANDIDATES_DETAILS_PAGE
CANDIDATES_DETAILS_PAGE,
SELECTION_PROCESS_PAGE,
SELECTION_PROCESS_OF_APPLICANT_PAGE
} from "./constants/pages";

// import LoginPage from './pages/LoginPage/LoginPage';
@@ -34,6 +36,8 @@ import CandidatesPage from './pages/CandidatesPage/CandidatesPage'
import AdDetailsPage from "./pages/AdsPage/AdDetailsPage";
import UserDetails from "./pages/UsersPage/UserDetails";
import CandidateDetailsPage from "./pages/CandidateDetailsPage/CandidateDetailsPage";
import SelectionProcessPage from "./pages/SelectionProcessPage/SelectionProcessPage";
import SelectionProcessOfApplicantPage from "./pages/SelectionProcessPage/SelectionProcessOfApplicantPage";

const AppRoutes = () => (
<Switch>
@@ -54,6 +58,8 @@ const AppRoutes = () => (
<PrivateRoute exact path={USERS_PAGE} component={UsersPage} />
<PrivateRoute exact path={CANDIDATES_PAGE} component={CandidatesPage} />
<PrivateRoute exact path={CANDIDATES_DETAILS_PAGE} component={CandidateDetailsPage} />
<PrivateRoute exact path={SELECTION_PROCESS_PAGE} component={SelectionProcessPage} />
<PrivateRoute exact path={SELECTION_PROCESS_OF_APPLICANT_PAGE} component={SelectionProcessOfApplicantPage} />
<Redirect from="*" to={NOT_FOUND_PAGE} />
</Switch>
);

+ 366
- 0
src/assets/styles/components/_selectionProcessPage.scss 查看文件

@@ -0,0 +1,366 @@
.selections {
margin-top: 36px;
padding-left: 72px;

@include media-below($bp-xl) {
padding-left: 36px !important;
}
}

.level-header {
padding-left: 81px;
display: flex;
justify-content: space-between;
align-items: center;

@include media-below($bp-xl) {
padding: 0 0.75rem !important;
}
}

.selection-levels {
overflow-x: scroll;
padding-bottom: 100px;
}

.level-header-subheader {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-size: 24px;
line-height: 36px;
padding-left: 0.3rem;
color: #226CB0;
letter-spacing: 0.02em;
}

.level-header-spliter {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-size: 24px;
line-height: 36px;
padding-left: 0.3rem;
color: #272727;
letter-spacing: 0.02em;
}

.selection-levels-processes {
display: flex;
margin-top: 39px;
position: relative;
}

.selection-levels-processes-process {
padding-left: 81px;
display: flex;

@include media-below($bp-xl) {
padding-left: 0;
}
}


.selection-card {
display: flex;
flex-direction: column;
justify-content: start;
align-items: left;
height: fit-content;
padding: 36px;
background: #F4F4F4;
border: 1px solid #e4e4e4;
border-radius: 18px;
gap: 18px;
margin-right: 36px;

@include media-below($bp-xl) {
margin-right: 20px !important;
padding: 36px !important;
}
}

.sel-item:hover {
scale: 1.05;
border-color: $mainBlue !important;
background-color: $mainBlueLight !important;
}

.bg-danger {
background-color: #272727;
}

.grey {
color: #e4e4e4
}

.selection-item {
display: flex;
flex-direction: row;
justify-content: left;
vertical-align: top;
align-items: left;
width: 400px;
padding: 18px 36px;
background: #FFFFFF;
border: 1px solid #e4e4e4;
border-radius: 18px;
gap: 18px;
margin-right: 36px;
}

.selection-item-date p {
text-align: right;
font-family: "Source Sans Pro";
font-style: normal;
font-weight: 400;
font-size: 16px;
line-height: 15px;
color: #272727;
flex: none;
order: 4;
flex-grow: 0;
}

.selection-card-title h3 {
font-family: "Source Sans Pro";
font-style: normal;
font-weight: 600;
font-size: 32px;
line-height: 32px;
letter-spacing: 0.02em;
color: #272727;
flex: none;
order: 0;
flex-grow: 0;
}

.selection-item-name,
.selection-item-date {
margin: auto 0 !important;
}

.selection-item-name p {
height: 20px;
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-size: 16px;
line-height: 20px;
text-align: right;
color: #226CB0;
flex: none;
order: 2;
flex-grow: 0;
}

.selection-item-buttons button {
box-sizing: border-box;
display: flex;
flex-direction: row;
justify-content: center;
font-size: 16px;
align-items: center;
padding: 9px;
gap: 10px;
min-width: 76px;
height: 38px;
border: 1px solid #e4e4e4;
border-radius: 9px;
flex: none;
order: 0;
flex-grow: 0;
}

.sel-item {
display: flex;
flex-direction: row;
align-items: center;
padding: 18px 36px;
gap: 18px;
cursor: pointer;
width: 458px;
background: #FFFFFF;
border: 1px solid #E4E4E4;
border-radius: 18px;
transition: 0.3s;
}

.sel-item .status {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
font-size: 16px;
line-height: 20px;
color: #272727;
flex: none;
order: 0;
flex-grow: 0;
}

.sel-item .date {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
font-size: 16px;
line-height: 20px;
color: #272727;
}

.sel-item .full-name {
height: 20px;
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-size: 16px;
text-align: right;
color: #226CB0;
flex: 3 0 auto;
order: 1;
}


.sel-item .status button {
box-sizing: border-box;
display: flex;
flex-direction: row;
justify-content: center;
font-size: 16px;
align-items: center;
padding: 9px;
gap: 10px;
min-width: 76px;
height: 38px;
border: 1px solid #e4e4e4;
background: white;
border-radius: 9px;
flex: none;
order: 0;
flex-grow: 0;
}

.active-process {
scale: 1.05;
border-color: $mainBlue !important;
background-color: $mainBlueLight !important;
}

.active-process-card {
display: flex;
flex-direction: column;
justify-content: start;
align-items: flex-start;
padding: 18px;
background: #ffffff;
border: 1px solid #e4e4e4;
border-radius: 18px;
gap: 18px;
margin-right: 36px;

@include media-below($bp-xl) {
margin-right: 20px !important;
padding: 36px !important;
}
}

.active-process-card-header {
display: flex;
flex-direction: row;
justify-content: start;
align-items: flex-start;
padding: 0px;
gap: 18px;
}

.active-process-card-body {
display: flex;
flex-direction: column;
justify-content: center;
width: 100%;
align-items: center;
padding: 34px;
gap: 18px;

@include media-below($bp-xl) {
margin-right: 20px !important;
padding: 36px !important;
}
}

.active-process-card-date p {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
font-size: 16px;
line-height: 20px;
color: #272727;
flex: none;
order: 0;
flex-grow: 0;
z-index: 0;
}

.active-process-card-number p {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-size: 16px;
line-height: 20px;
text-align: left;
background: conic-gradient(from 73.66deg at 50% 50%, #226CB0 0deg, #BA6FB9 106.88deg, #5E9FDB 228.75deg, #226CB0 360deg);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-fill-color: transparent;
flex: none;
order: 6;
flex-grow: 0;
z-index: 6;
}

.active-process-card-buttons {
overflow: hidden;
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: center;
padding: 0px;
gap: 18px;
flex: none;
order: 4;
flex-grow: 0;
@include media-below($bp-xl) {
gap: 9px !important;
}
}


.active-process-card-buttons button {
box-sizing: border-box;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
padding: 9px;
gap: 10px;
height: 38px;
background: transparent;
border: 1px solid #e4e4e4;
border-radius: 9px;
flex: none;
order: 0;
flex-grow: 0;
}

.active-process-card-link{
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
font-size: 12px;
text-decoration-line: underline;
color: #226CB0;
flex: none;
order: 4;
flex-grow: 0;
z-index: 4;
}

+ 68
- 0
src/components/Selection/ApplicantSelection.js 查看文件

@@ -0,0 +1,68 @@
import React from "react";
import PropTypes from "prop-types";
import { formatDate } from "../../util/helpers/dateHelpers";
import { Link } from "react-router-dom";

const ApplicantSelection = ({
levelNumber,
levelName,
schedguler,
link,
date,
status,
id,
onShowAdDetails,
className,
}) => {
return (
<div className={`active-process-card ${className}`} onClick={onShowAdDetails}>
<div className="active-process-card-header">
<div className="active-process-card-number">
<p>
{levelNumber}
</p>
</div>
</div>
<div className="active-process-card-body">
<div className="active-process-card-date">
<p>
{date && date !== "" && formatDate(new Date(date))}
</p>
</div>
<div className="ad-card-title">
<h3>{levelName}</h3>
</div>

<div className="active-process-card-date">
<p>
{schedguler}
</p>
</div>

<div className="active-process-card-buttons">
<button>{status}</button>
</div>
<div className="active-process-card-link">
{(status === "Odrađen") && <Link className="ad-details-buttons-link" to={"/candidates/" + id}>Detaljni izveštaja</Link>}
{(link && status !== "Odrađen") && <a href={link} className="ad-details-buttons-link">Link do Google Meet-a</a>}
</div>
</div>
</div>
);
};

ApplicantSelection.propTypes = {
id: PropTypes.string,
levelNumber: PropTypes.number,
levelName: PropTypes.string,
schedguler: PropTypes.string,
link: PropTypes.string,
date: PropTypes.any,
status: PropTypes.string,
onShowAdDetails: PropTypes.func,
className: PropTypes.any,
};

export default ApplicantSelection;

+ 99
- 0
src/components/Selection/Selection.js 查看文件

@@ -0,0 +1,99 @@
import React from "react";
import PropTypes from "prop-types";
import { selectDoneProcessError } from "../../store/selectors/processSelectors";
import { setDoneProcessReq } from "../../store/actions/processes/processAction";
import { useDispatch, useSelector } from "react-redux";
import { formatDateSrb, formatTimeSrb } from "../../util/helpers/dateHelpers";
import { SELECTION_PROCESS_OF_APPLICANT_PAGE } from "../../constants/pages";

const dragStart = (e, applicant) => {
e.dataTransfer.setData("text/plain", JSON.stringify(applicant));
}

const dragOver = (e) => {
e.preventDefault();
}

const Selection = (props) => {
const applicants = props.selection.selectionProcesses;
const errorMessage = useSelector(selectDoneProcessError);
const dispatch = useDispatch();


const dropItem = (e, selId) => {
var data = e.dataTransfer.getData("text/plain");
const selectionProcess = JSON.parse(data);
if (selectionProcess.selectionLevelId !== selId) {
dispatch(setDoneProcessReq({ id: selectionProcess.id }));
}
if (errorMessage) {
console.log(errorMessage)
}
}

const handleOpenDetails = (id) => {
props.history.push(SELECTION_PROCESS_OF_APPLICANT_PAGE.replace(":id", id))
}

const renderList = applicants?.map((item, index) => {
return <div draggable key={index} className="sel-item" onDragStart={e => dragStart(e, item)}
onClick={() => handleOpenDetails(item.applicant.applicantId)}>
<div className="status">
<button>{item.status}</button>
</div>
<div className="date">
<p>{formatDateSrb(item.date)} <span className="grey">|</span> {formatTimeSrb(item.date)}</p>
</div>
<div className="full-name">
<p>{item.applicant.firstName + " " + item.applicant.lastName}</p>
</div>
</div>
}
);

return (
<div dropppable="true" id={props.selection.id} className="selection-card"
onDragOver={e => dragOver(e)}
onDrop={e => dropItem(e, props.selection.id)}
>
<div className="selection-card-title">
<h3>{props.selection.name}</h3>
</div>

{applicants.length > 0 && renderList}
{applicants.length === 0 && <div className="sel-item">
<div className="date">
<p>Nema kandidata u selekciji</p>
</div>
</div>}
</div>
);
};

Selection.propTypes = {
history: PropTypes.shape({
replace: PropTypes.func,
push: PropTypes.func,
location: PropTypes.shape({
pathname: PropTypes.string,
}),
}),
selection: PropTypes.shape({
id: PropTypes.number,
name: PropTypes.string,
selectionProcesses: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.number,
name: PropTypes.string,
date: PropTypes.string,
status: PropTypes.string,
currentSelection: PropTypes.number,
map: PropTypes.func,
applicant: PropTypes.shape({
firstName: PropTypes.string,
lastName: PropTypes.string
})
}))
}),
};

export default Selection;

+ 3
- 1
src/constants/pages.js 查看文件

@@ -10,4 +10,6 @@ export const USER_DETAILS_PAGE = '/users/:id';
export const CANDIDATES_PAGE = '/candidates';
export const CANDIDATES_DETAILS_PAGE = '/candidates/:id';
export const FORGOT_PASSWORD_CONFIRMATION_PAGE = '/forgot-password-confirmation';
export const RESET_PASSWORD_PAGE = '/reset-password';
export const RESET_PASSWORD_PAGE = '/reset-password';
export const SELECTION_PROCESS_PAGE = '/selectionFlow';
export const SELECTION_PROCESS_OF_APPLICANT_PAGE = '/selectionflow/:id';

+ 19
- 15
src/i18n/resources/rs.js 查看文件

@@ -107,26 +107,30 @@ export default {
// ClientIpAddressIsNullOrEmpty:"Client Ip address is null or empty",
// UsernameDoesNotExist: "Username does not exist"
// },

nav: {
ads: "Oglasi",
selectionFlow: "Tok Selekcije",
candidates: "Kandidati",
planer: "Planer",
patterns: "Šabloni",
stats: "Statistika",
users: "Korisnici",
signOut: "Izloguj se",
ads: 'Oglasi',
selectionFlow: 'Tok Selekcije',
candidates: 'Kandidati',
planer: 'Planer',
patterns: 'Šabloni',
stats: 'Statistika',
users: 'Korisnici',
signOut: 'Izloguj se'
},
ads: {
activeAds: "Aktivni Oglasi",
archiveAds: "Arhiva",
adDetailsDescription:
"Tim Diligent neprestano raste! Tražimo timskog igrača koji će raditi s iskusnim inženjerima. Ako je tehnologija vaša strast i spremni ste svaki dan pomicati granice svog znanja, onda je Diligent pravo mesto za vas. Ukoliko niste iz Niša, nudimo full-time remote poziciju.",
adDetailsExperience: "godina iskustva",
adDetailsExpiredAt: "Rok prijave do",
adDetailsKeyResponsibilities: "Ključne Odgovornosti",
adDetailsRequirements: "Zahtevi",
adDetailsOffer: "Šta Nudimo",
archiveAdsCandidates: "Prijavljeni kandidati",
},
adDetailsExperience: "godina iskustva",
adDetailsExpiredAt: "Rok prijave do",
adDetailsKeyResponsibilities: "Ključne Odgovornosti",
adDetailsRequirements: "Zahtevi",
adDetailsOffer: "Šta Nudimo",
archiveAdsCandidates: "Prijavljeni kandidati",
},
selection: {
title: "Tok Selekcije"
}
};

+ 1
- 0
src/main.scss 查看文件

@@ -17,6 +17,7 @@
@import './assets/styles/components/user-profile';
@import './assets/styles/components/auth';
@import './assets/styles/components/login';
@import './assets/styles/components/selectionProcessPage';
@import './assets/styles/components/login-card';
@import './assets/styles/components/forgot-password';
@import './assets/styles/components/input';

+ 4
- 5
src/pages/CandidateDetailsPage/CandidateDetailsPage.js 查看文件

@@ -43,7 +43,7 @@ const CandidateDetailsPage = () => {
};

const getArray = () => {
let newArray = users.map(function (value) {
let newArray = users?.map(function (value) {
return { id: value.id, display: value.firstName + value.lastName };
});

@@ -51,7 +51,6 @@ const CandidateDetailsPage = () => {
};

const tranformDisplay = (id, display) => {
console.log(id);
return "@" + display + " ";
};

@@ -110,7 +109,7 @@ const CandidateDetailsPage = () => {
: "Experience:" + candidate.experience}
</p>
<div className="technologies-candidate-container">
{candidate.technologyApplicants.map((obj, index) => (
{candidate?.technologyApplicants?.map((obj, index) => (
<div className="technology-candidate-card" key={index}>
{obj.technology.name}
</div>
@@ -167,7 +166,7 @@ const CandidateDetailsPage = () => {
}}
ref={messageContainer}
>
{candidate.comments.map((comment, index) => (
{candidate?.comments?.map((comment, index) => (
<div className="comment-sub-container" key={index}>
<div className="comment-sender">
<p>
@@ -236,7 +235,7 @@ const CandidateDetailsPage = () => {
<div className="applicant-ads-container">
<p>Sve prijave</p>
<div className="applicant-ads-sub-container">
{candidate.ads.map((add, index) => (
{candidate?.ads?.map((add, index) => (
<div key={index} className="applicant-add">
<p className="applicant-add-date">
{formatDate(add.createdAt)}

+ 194
- 0
src/pages/SelectionProcessPage/SelectionProcessOfApplicantPage.js 查看文件

@@ -0,0 +1,194 @@
import React, { useState, useEffect, useRef } from "react";
import arrow_left from "../../assets/images/arrow_left.png";
import arrow_right from "../../assets/images/arrow_right.png";
import { useParams } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import AddAdModal from "../../components/Ads/AddAdModal";
import AdFilters from "../../components/Ads/AdFilters";
import { useDispatch } from "react-redux";
import { setApplicantProcessesReq } from "../../store/actions/processes/applicantProcessesAction";
import { useSelector } from "react-redux";
import { selectApplicantProcesses } from "../../store/selectors/applicantProcessesSelectors";
import FilterButton from "../../components/Button/FilterButton";
import Slider from "react-slick";
import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css";
import { useTheme } from "@emotion/react";
import { useMediaQuery } from "@mui/material";
import ApplicantSelection from "../../components/Selection/ApplicantSelection";
import { setProcessesReq } from "../../store/actions/processes/processesAction"
import { selectProcesses } from "../../store/selectors/processesSelectors"

const SelectionProcessOfApplicantPage = () => {
const theme = useTheme();
const matches = useMediaQuery(theme.breakpoints.down("sm"));
const [toggleFiltersDrawer, setToggleFiltersDrawer] = useState(false);
const [toggleModal, setToggleModal] = useState(false);
const processes = useSelector(selectApplicantProcesses);
const levels = useSelector(selectProcesses)
const { id } = useParams();
const activeAdsSliderRef = useRef();
const { t } = useTranslation();
const dispatch = useDispatch();

useEffect(() => {
dispatch(setApplicantProcessesReq(id));
dispatch(setProcessesReq());
}, []);

const handleToggleFiltersDrawer = () => {
setToggleFiltersDrawer((oldState) => !oldState);
};

const handleToggleModal = () => {
setToggleModal((oldState) => !oldState);
};

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

const activeAdsArrowLeftHandler = () => {
activeAdsSliderRef.current.slickPrev();
};

const activeAdsArrowRightHandler = () => {
activeAdsSliderRef.current.slickNext();
};


const concatLevels = () => {
const applicantSelections = [];
for (var i = processes.length; i < levels.length; i++) {
applicantSelections.push(<ApplicantSelection
levelNumber={levels[i].id}
levelName={levels[i].name}
schedguler={""}
date={""}
status={"Čeka na zakazivanje"}
id={id}
key={i} />);
}
applicantSelections.push(<ApplicantSelection key={i} className="hiddenAd"
date={""} />);
return applicantSelections;
};

return (
<>
<div className="l-t-rectangle"></div>
<div className="r-b-rectangle"></div>
{/* <AdFilters /> */}
<AdFilters
open={toggleFiltersDrawer}
handleClose={handleToggleFiltersDrawer}
/>
<AddAdModal open={toggleModal} handleClose={handleToggleModal} />
<div className="ads">
{processes && processes.length > 0 && (
<div className="active-ads">
<div className="active-ads-header">
<h1>{t("selection.title")}
<span className="level-header-spliter">
|
</span>
<span className="level-header-subheader">
{id}
</span>
</h1>
<FilterButton onShowFilters={handleToggleFiltersDrawer} />
</div>
<div className="active-ads-ads">
<div className="active-ads-ads-a">
{!matches && (
<div className="active-ads-ads-arrows">
<button onClick={activeAdsArrowLeftHandler}>
<img src={arrow_left} alt="arrow-left" />
</button>
<button onClick={activeAdsArrowRightHandler}>
<img src={arrow_right} alt="arrow-right" />
</button>
</div>
)}
</div>
<div className="active-ads-ads-ad">
<Slider
{...settings}
slidesToShow={4}
slidesToScroll={4}
style={{ width: "100%" }}
ref={activeAdsSliderRef}
>
{processes.map((process, index) =>
{
return <ApplicantSelection
levelNumber={index + 1}
levelName={process.selectionLevel.name}
schedguler={"SAfet Purkovic"}
link={process.link}
date={new Date(process.date)}
status={process.status}
id={id}
key={index}
className={index === processes.length - 1 ? "active-process" : ""}
/>
})}
{processes.length < levels.length && concatLevels()}
</Slider>
</div>
</div>
</div>
)}
{matches && (
<div className="active-ads-ads-arrows">
<button onClick={activeAdsArrowLeftHandler}>
<img src={arrow_left} alt="arrow-left" />
</button>
<button onClick={activeAdsArrowRightHandler}>
<img src={arrow_right} alt="arrow-right" />
</button>
</div>
)}
</div>
<div className="add-ad">
<Link className="ad-details-buttons-link" to="/selectionflow">
Nazad na sve kandidate
</Link>
</div>
</>
);
};



export default SelectionProcessOfApplicantPage;

+ 101
- 0
src/pages/SelectionProcessPage/SelectionProcessPage.js 查看文件

@@ -0,0 +1,101 @@
import React, { useState, useEffect } from "react";
import { useSelector} from 'react-redux';
import Selection from "../../components/Selection/Selection";
import IconButton from "../../components/IconButton/IconButton";
import filterVector from "../../assets/images/filter_vector.png";
import { useTranslation } from "react-i18next";
import AddAdModal from "../../components/Ads/AddAdModal";
import { useDispatch } from "react-redux";
import AdFilters from "../../components/Ads/AdFilters";
import { setDoneProcess } from "../../store/actions/processes/processAction";
import { setProcessesReq } from "../../store/actions/processes/processesAction";
import { selectDoneProcess } from "../../store/selectors/processSelectors";
import { selectProcesses } from "../../store/selectors/processesSelectors"
import PropTypes from "prop-types";

const SelectionProcessPage = ({history}) => {
const [toggleFiltersDrawer, setToggleFiltersDrawer] = useState(false);
const [toggleModal, setToggleModal] = useState(false);
// const errorMessage = useSelector(selectProcessesError);
const process = useSelector(selectDoneProcess);
const processes = useSelector(selectProcesses);
// const doneErrorMessage = useSelector(selectDoneProcessError);
const { t } = useTranslation();
const dispatch = useDispatch();

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


useEffect(() => {
dispatch(setProcessesReq());
dispatch(setDoneProcess(false));
},[process.process.doneProcess])

const handleToggleFiltersDrawer = () => {
setToggleFiltersDrawer((oldState) => !oldState);
};

const handleToggleModal = () => {
setToggleModal((oldState) => !oldState);
};

const renderList = processes.map((item, index) => {
return <Selection selection={item} key={index} history={history}/>
}
);

return (
<>
<div className="l-t-rectangle"></div>
<div className="r-b-rectangle"></div>
<AdFilters />
<AdFilters
open={toggleFiltersDrawer}
handleClose={handleToggleFiltersDrawer}
/>
<AddAdModal open={toggleModal} handleClose={handleToggleModal} />
<div className="selections">
<div className="selection-levels">
<div className="level-header">
<h1>{t("selection.title")}
<span className="level-header-spliter">
|
</span>
<span className="level-header-subheader">
Svi kandidati
</span>
</h1>
<IconButton
sx={{ marginLeft: "15px" }}
className="c-btn c-btn--primary-outlined"
onClick={handleToggleFiltersDrawer}
>
Filteri{" "}
<img src={filterVector} alt="filter" className="filter-vector" />
</IconButton>
</div>
<div className="selection-levels-processes">
<div className="selection-levels-processes-process">
{renderList}
</div>
</div>
</div>
</div>
</>
);
};

SelectionProcessPage.propTypes = {
history: PropTypes.shape({
replace: PropTypes.func,
push: PropTypes.func,
location: PropTypes.shape({
pathname: PropTypes.string,
}),
}),
};

export default SelectionProcessPage;

+ 7
- 1
src/request/apiEndpoints.js 查看文件

@@ -24,5 +24,11 @@ export default {
},
comments:{
addComment:base + '/comments'
}
},
processes: {
allLevels: base + "/selectionlevels",
doneProcess: base + "/selectionprocesses",
getApplicantProcesses: base + "/applicants/processes",
// allProcesses: base + "/selectionprocesses",
},
};

+ 6
- 0
src/request/processesReguest.js 查看文件

@@ -0,0 +1,6 @@
import { getRequest } from ".";
import apiEndpoints from "./apiEndpoints";

export const getAllLevels = () => getRequest(apiEndpoints.processes.allLevels);
export const doneProcess = (id) => getRequest(`${apiEndpoints.processes.doneProcess}/${id}`);
export const getProcessesOfApplicant = (id) => getRequest(`${apiEndpoints.processes.getApplicantProcesses}/${id}`);

+ 20
- 0
src/store/actions/processes/applicantProcessesAction.js 查看文件

@@ -0,0 +1,20 @@
import {
FETCH_APPLICANT_PROCESSES_REQ,
FETCH_APPLICANT_PROCESSES_ERR,
FETCH_APPLICANT_PROCESSES_SUCCESS,
} from "./processesActionConstants";
export const setApplicantProcessesReq = (payload) => ({
type: FETCH_APPLICANT_PROCESSES_REQ,
payload
});
export const setApplicantProcessesError = (payload) => ({
type: FETCH_APPLICANT_PROCESSES_ERR,
payload,
});
export const setApplicantProcesses = (payload) => ({
type: FETCH_APPLICANT_PROCESSES_SUCCESS,
payload,
});

+ 21
- 0
src/store/actions/processes/processAction.js 查看文件

@@ -0,0 +1,21 @@
import {
PUT_PROCESS_ERR,
PUT_PROCESS_REQ,
PUT_PROCESS_SUCCESS
} from "./processesActionConstants";
export const setDoneProcessReq = (payload) => ({
type: PUT_PROCESS_REQ,
payload
});
export const setDoneProcessError = (payload) => ({
type: PUT_PROCESS_ERR,
payload,
});
export const setDoneProcess = (payload) => ({
type: PUT_PROCESS_SUCCESS,
payload
});

+ 19
- 0
src/store/actions/processes/processesAction.js 查看文件

@@ -0,0 +1,19 @@
import {
FETCH_PROCESSES_REQ,
FETCH_PROCESSES_ERR,
FETCH_PROCESSES_SUCCESS,
} from "./processesActionConstants";

export const setProcessesReq = () => ({
type: FETCH_PROCESSES_REQ,
});

export const setProcessesError = (payload) => ({
type: FETCH_PROCESSES_ERR,
payload,
});

export const setProcesses = (payload) => ({
type: FETCH_PROCESSES_SUCCESS,
payload,
});

+ 9
- 0
src/store/actions/processes/processesActionConstants.js 查看文件

@@ -0,0 +1,9 @@
export const FETCH_PROCESSES_REQ = 'FETCH_PROCESSES_REQ';
export const FETCH_PROCESSES_ERR = 'FETCH_PROCESSES_ERR';
export const FETCH_PROCESSES_SUCCESS = 'FETCH_PROCESSES_SUCCESS';
export const PUT_PROCESS_REQ = 'PUT_PROCESS_REQ';
export const PUT_PROCESS_ERR = 'PUT_PROCESS_ERR';
export const PUT_PROCESS_SUCCESS = 'PUT_PROCESS_SUCCESS';
export const FETCH_APPLICANT_PROCESSES_REQ = 'FETCH_APPLICANT_PROCESSES_REQ';
export const FETCH_APPLICANT_PROCESSES_ERR = 'FETCH_APPLICANT_PROCESSES_ERR';
export const FETCH_APPLICANT_PROCESSES_SUCCESS = 'FETCH_APPLICANT_PROCESSES_SUCCESS';

+ 8
- 2
src/store/reducers/index.js 查看文件

@@ -4,11 +4,14 @@ import loadingReducer from './loading/loadingReducer';
import userReducer from './user/userReducer';
import randomDataReducer from './randomData/randomDataReducer';
import usersReducer from './user/usersReducer';
import candidatesReducer from './candidates/candidatesReducer';
import candidateReducer from './candidate/candidateReducer';
import adsReducer from "./ad/adsReducer";
import adReducer from "./ad/adReducer";
import archiveAdsReducer from "./ad/archiveAdsReducer";
import candidatesReducer from "./candidates/candidatesReducer";
import processesReducer from './processes/processesReducer';
import processReducer from "./processes/processReducer";
import applicantProcessesReducer from "./processes/applicantProcessesReducer";

export default combineReducers({
login: loginReducer,
@@ -16,9 +19,12 @@ export default combineReducers({
loading: loadingReducer,
randomData: randomDataReducer,
users: usersReducer,
candidates:candidatesReducer,
candidate:candidateReducer,
ads: adsReducer,
ad: adReducer,
archiveAds: archiveAdsReducer,
candidates: candidatesReducer,
processes: processesReducer,
process: processReducer,
applicantProcesses: applicantProcessesReducer
});

+ 32
- 0
src/store/reducers/processes/applicantProcessesReducer.js 查看文件

@@ -0,0 +1,32 @@
import {
FETCH_APPLICANT_PROCESSES_ERR,
FETCH_APPLICANT_PROCESSES_SUCCESS
} from "../../actions/processes/processesActionConstants";
import createReducer from "../../utils/createReducer";
const initialState = {
applicantProcesses: [],
errorMessage: ""
};
export default createReducer(
{
[FETCH_APPLICANT_PROCESSES_SUCCESS]: setStateApplicantProcesses,
[FETCH_APPLICANT_PROCESSES_ERR]: setApplicantProcessesErrorMessage,
},
initialState
);
function setStateApplicantProcesses(state, action) {
return {
...state,
applicantProcesses: action.payload,
};
}
function setApplicantProcessesErrorMessage(state, action) {
return {
...state,
errorMessage: action.payload,
};
}

+ 33
- 0
src/store/reducers/processes/processReducer.js 查看文件

@@ -0,0 +1,33 @@
import {
PUT_PROCESS_ERR,
PUT_PROCESS_SUCCESS
} from "../../actions/processes/processesActionConstants";
import createReducer from "../../utils/createReducer";
const initialState = {
doneProcess: false,
errorMessage: "",
};
export default createReducer(
{
[PUT_PROCESS_SUCCESS]: setStateDoneProcess,
[PUT_PROCESS_ERR]: setDoneProcessErrorMessage,
},
initialState
);
function setStateDoneProcess(state, action) {
return {
...state,
doneProcess: action.payload,
};
}
function setDoneProcessErrorMessage(state, action) {
return {
...state,
errorMessage: action.payload,
};
}

+ 32
- 0
src/store/reducers/processes/processesReducer.js 查看文件

@@ -0,0 +1,32 @@
import {
FETCH_PROCESSES_ERR,
FETCH_PROCESSES_SUCCESS
} from "../../actions/processes/processesActionConstants";
import createReducer from "../../utils/createReducer";

const initialState = {
processes: [],
errorMessage: "",
};

export default createReducer(
{
[FETCH_PROCESSES_SUCCESS]: setStateProcesses,
[FETCH_PROCESSES_ERR]: setProcessesErrorMessage,
},
initialState
);

function setStateProcesses(state, action) {
return {
...state,
processes: action.payload,
};
}

function setProcessesErrorMessage(state, action) {
return {
...state,
errorMessage: action.payload,
};
}

+ 3
- 1
src/store/saga/index.js 查看文件

@@ -3,12 +3,14 @@ import adsSaga from "./adsSaga";
import candidatesSaga from './candidatesSaga';
import loginSaga from "./loginSaga";
import usersSaga from "./usersSaga";
import processesSaga from "./processSaga";

export default function* rootSaga() {
yield all([
loginSaga(),
usersSaga(),
adsSaga(),
candidatesSaga()
candidatesSaga(),
processesSaga(),
]);
}

+ 0
- 3
src/store/saga/loginSaga.js 查看文件

@@ -76,7 +76,6 @@ function* fetchUser({ payload }) {

function* forgetPassword({ payload }) {
try {
// console.log(payload)
const { data } = yield call(forgetPasswordEmailSend, payload);
yield put(forgetPasswordSuccess(data));
if (payload.handleApiResponseSuccess) {
@@ -98,9 +97,7 @@ function* forgetPassword({ payload }) {

function* resetPassword({ payload }) {
try {
// console.log(payload)
const { data } = yield call(sendResetPassword, payload);
// console.log(data);
yield put(forgetPasswordSuccess(data));
if (payload.handleApiResponseSuccess) {
yield call(payload.handleApiResponseSuccess);

+ 41
- 0
src/store/saga/processSaga.js 查看文件

@@ -0,0 +1,41 @@
import { all, call, put, takeLatest } from "redux-saga/effects";
import { getAllLevels, doneProcess, getProcessesOfApplicant } from "../../request/processesReguest";
import { setProcesses, setProcessesError } from "../actions/processes/processesAction";
import { setDoneProcess, setDoneProcessError } from "../actions/processes/processAction";
import { setApplicantProcesses, setApplicantProcessesError } from "../actions/processes/applicantProcessesAction";
import { FETCH_PROCESSES_REQ, PUT_PROCESS_REQ, FETCH_APPLICANT_PROCESSES_REQ } from "../actions/processes/processesActionConstants";

export function* getProcesses() {
try {
const result = yield call(getAllLevels);
yield put(setProcesses(result.data));
} catch (error) {
yield put(setProcessesError(error));
}
}

export function* finishProcess(payload) {
try {
const id = payload.payload.id;
const result = yield call(doneProcess,id);
yield put(setDoneProcess(result.data));
} catch (error) {
yield put(setDoneProcessError(error));
}
}

export function* getApplicantProcesses(payload) {
try {
const id = payload.payload;
const result = yield call(getProcessesOfApplicant,id);
yield put(setApplicantProcesses(result.data.selectionProcesses));
} catch (error) {
yield put(setApplicantProcessesError(error));
}
}

export default function* processesSaga() {
yield all([takeLatest(FETCH_PROCESSES_REQ, getProcesses)]);
yield all([takeLatest(PUT_PROCESS_REQ, finishProcess)]);
yield all([takeLatest(FETCH_APPLICANT_PROCESSES_REQ, getApplicantProcesses)]);
}

+ 10
- 0
src/store/selectors/applicantProcessesSelectors.js 查看文件

@@ -0,0 +1,10 @@
import { createSelector } from "@reduxjs/toolkit";

export const applicantProcessesSelector = (state) => state.applicantProcesses;

export const selectApplicantProcesses = createSelector(applicantProcessesSelector, (state) => state.applicantProcesses);

export const selectApplicantProcessesError = createSelector(
applicantProcessesSelector,
(state) => state.errorMessage
);

+ 10
- 0
src/store/selectors/processSelectors.js 查看文件

@@ -0,0 +1,10 @@
import { createSelector } from "@reduxjs/toolkit";

export const doneProcessSelector = (state) => state;

export const selectDoneProcess = createSelector(doneProcessSelector, (state) => state);

export const selectDoneProcessError = createSelector(
doneProcessSelector,
(state) => state.errorMessage
);

+ 10
- 0
src/store/selectors/processesSelectors.js 查看文件

@@ -0,0 +1,10 @@
import { createSelector } from "@reduxjs/toolkit";

export const processesSelector = (state) => state.processes;

export const selectProcesses = createSelector(processesSelector, (state) => state.processes);

export const selectProcessesError = createSelector(
processesSelector,
(state) => state.errorMessage
);

+ 10
- 0
src/util/helpers/dateHelpers.js 查看文件

@@ -38,3 +38,13 @@ export function formatDateRange(dates) {
const end = formatDate(dates.end);
return i18next.t('common.date.range', { start, end });
}

export function formatDateSrb(date) {
const dt = new Date(date);
return format(dt, 'dd.MM.');
}

export function formatTimeSrb(date) {
const dt = new Date(date);
return format(dt, 'HH.mm.');
}

Loading…
取消
儲存