Explorar el Código

implemented candidate rejection with comments

pull/132/head
meris.ahmatovic hace 3 años
padre
commit
4ed2fc261a

BIN
src/assets/images/unsucc.png Ver fichero


+ 3
- 4
src/assets/styles/components/_icon-button.scss Ver fichero

height: 34px !important; height: 34px !important;
min-width: none !important; min-width: none !important;
} }
.td-btn.active{
background-color: rgba(100,255,100,0.75) !important;
}
.td-btn.inactive{ .td-btn.inactive{
background-color: rgba(255,100,100,0.75) !important;
background: #FFEAEE !important;
color: #D72228 !important;
border-color: #D72228 !important;
} }


.userPageBtn.activeEnable{ .userPageBtn.activeEnable{

+ 4
- 0
src/assets/styles/components/_modal.scss Ver fichero

font-size: 16px; font-size: 16px;
color: $mainBlack; color: $mainBlack;
text-align: center; text-align: center;

&.comment{
padding: 20px 0px 15px 0px;
}
} }

+ 11
- 3
src/assets/styles/components/_selectionProcessPage.scss Ver fichero

background-color: $mainBlueLight !important; background-color: $mainBlueLight !important;
transition: .25s; transition: .25s;
.status button{ .status button{
background-color: $mainBlueLight !important;
transition: .25s; transition: .25s;
}
}
&.unsucc{
background: #FFEAEE !important;
color: #D72228 !important;
}
background-color: $mainBlueLight !important;
}}


} }


font-weight: 500 !important; font-weight: 500 !important;
text-transform: capitalize !important; text-transform: capitalize !important;
} }
.unsucc{
background: #FFEAEE !important;
color: #D72228 !important;
}


.active-process-card-buttons { .active-process-card-buttons {
overflow: hidden; overflow: hidden;

+ 183
- 0
src/components/MUI/CommentProcessDialog.js Ver fichero

import React, { useContext, useRef } from "react";
import PropTypes from "prop-types";
import x from "../../assets/images/x.png";
import { Editor } from "@tinymce/tinymce-react";
import {
Dialog,
DialogTitle,
DialogActions,
useMediaQuery,
useTheme,
DialogContent,
// TextField,
// TextField,
} from "@mui/material";
import IconButton from "../IconButton/IconButton";
// import { DateTimePicker } from "@mui/x-date-pickers/DateTimePicker";
import { useDispatch, useSelector } from "react-redux";
// import { format, isValid } from "date-fns";
// import { fetchInitProcess } from "../../store/actions/candidates/candidatesActions";
import { useEffect } from "react";
import { SelectionContext } from "../../context/SelectionContext";
import { setUpdateStatusReq } from "../../store/actions/processes/processAction";
// import { setUpdateInterviewerReq } from "../../store/actions/processes/processAction";
// import { setUpdateStatusReq } from "../../store/actions/processes/processAction";

const CommentProcessDialog = ({
title,
subtitle,
imgSrc,
onClose,
open,
maxWidth,
fullWidth,
responsive,
}) => {
const { activeProcessUnsuccess, setActiveProcessUnsuccess } = useContext(SelectionContext);

const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down("md"));

const editorContent = useRef();

const { isSuccess } = useSelector((s) => s.interviewerUpdate);

const dispatch = useDispatch();

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

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

const submitHandler = () => {
dispatch(
setUpdateStatusReq({
data: {
comment: editorContent.current.getContent(),
newStatus: "Neuspešno",
processId: activeProcessUnsuccess.id,
},
responseHandler: apiSuccess,
})
);
};

const apiSuccess = () => {
setActiveProcessUnsuccess(null);
};

return (
<Dialog
maxWidth={maxWidth}
keepMounted={false}
fullWidth={fullWidth}
fullScreen={responsive && fullScreen}
onClose={handleClose}
open={open}
style={{
padding: "0px",
}}
>
{/* {activeInterview?.id} */}
<div style={{ padding: "25px" }}>
<DialogTitle style={{ padding: 0 }}>
{fullScreen ? (
<>
<div className="flex-center" style={{ justifyContent: "start" }}>
<img
style={{
position: "relative",
top: -0.25,
paddingRight: "10px",
}}
src={imgSrc}
/>
<h5 style={{ textAlign: "start" }}>{title}</h5>
<div style={{ justifySelf: "stretch", flex: "1" }}></div>
<IconButton onClick={onClose}>
<img
style={{
position: "relative",
top: -0.25,
}}
src={x}
/>
</IconButton>
</div>
<p
className="dialog-subtitle"
style={{ paddingLeft: "23px", marginTop: "-10px" }}
>
| {subtitle}
</p>
</>
) : (
<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={imgSrc}
/>
<h5>{title}</h5>
<div className="vr"></div>
<p className="dialog-subtitle">{subtitle}</p>
</div>
</div>
)}
</DialogTitle>
<DialogContent>
<form className="modal-content comment">
<p style={{width:'100%', textAlign:'left', alignSelf:'flex-start', marginBottom:'10px'}}>Komentar (opciono):</p>
<Editor
onInit={(evt, editor) => (editorContent.current = editor)}
style={{ height: "20px" }}
/>
</form>
</DialogContent>
<DialogActions style={{ padding: 0, justifyContent: "space-between" }}>
{!fullScreen ? (
<IconButton
data-testid="editbtn"
className={`c-btn--primary-outlined interview-btn c-btn dialog-btn`}
onClick={onClose}
>
Otkaži
</IconButton>
) : (
""
)}
<IconButton
data-testid="editbtn"
className={`c-btn--primary-outlined sm-full interview-btn c-btn dialog-btn`}
onClick={submitHandler}
>
Potvrdi
</IconButton>
</DialogActions>
</div>
</Dialog>
);
};

CommentProcessDialog.propTypes = {
title: PropTypes.any,
subtitle: PropTypes.any,
imgSrc: PropTypes.any,
open: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
maxWidth: PropTypes.oneOf(["xs", "sm", "md", "lg", "xl"]),
fullWidth: PropTypes.bool,
responsive: PropTypes.bool,
};

export default CommentProcessDialog;

+ 3
- 3
src/components/MUI/InterviewDialog.js Ver fichero

responsive, responsive,
}) => { }) => {
const [selected, setSelected] = useState(""); const [selected, setSelected] = useState("");
const [selectedInterviewer, setSelectedInterviewer] = useState("");
const [selectedInterviewer, setSelectedInterviewer] = useState(null);
const [value, setValue] = useState(null); const [value, setValue] = useState(null);


const theme = useTheme(); const theme = useTheme();


useEffect(() => { useEffect(() => {
setSelected(""); setSelected("");
setSelectedInterviewer("");
setSelectedInterviewer(null);
setValue(null); setValue(null);
}, [onClose]); }, [onClose]);


<Select <Select
labelId="demo-simple-select-label" labelId="demo-simple-select-label"
id="demo-simple-select" id="demo-simple-select"
value={selectedInterviewer}
value={selectedInterviewer ? selectedInterviewer : ''}
label="Ime intervjuera (opciono)" label="Ime intervjuera (opciono)"
onChange={(e) => { onChange={(e) => {
setSelectedInterviewer(e.target.value); setSelectedInterviewer(e.target.value);

+ 5
- 1
src/components/Selection/ApplicantSelection.js Ver fichero

import { formatDate } from "../../util/helpers/dateHelpers"; import { formatDate } from "../../util/helpers/dateHelpers";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import success from "../../assets/images/svg/success.svg" import success from "../../assets/images/svg/success.svg"
import unsucc from "../../assets/images/unsucc.png"


const ApplicantSelection = ({ const ApplicantSelection = ({
levelNumber, levelNumber,
{(status === "Odrađen") && <div className="active-process-card-logo"> {(status === "Odrađen") && <div className="active-process-card-logo">
<img src={success} alt="success-image" /> <img src={success} alt="success-image" />
</div>} </div>}
{(status === "Neuspešno") && <div className="active-process-card-logo">
<img src={unsucc} alt="success-image" />
</div>}
</div> </div>


<div className="active-process-card-body"> <div className="active-process-card-body">
</div> </div>


<div className="active-process-card-buttons"> <div className="active-process-card-buttons">
<button>{status}</button>
<button className={status === "Neuspešno" && 'unsucc' }>{status}</button>
</div> </div>


<div className="active-process-card-link"> <div className="active-process-card-link">

+ 1
- 1
src/components/Selection/Selection.js Ver fichero

var data = e.dataTransfer.getData("text/plain"); var data = e.dataTransfer.getData("text/plain");
const selectionProcess = JSON.parse(data); const selectionProcess = JSON.parse(data);


if (selectionProcess.selectionLevelId < selId) {
if (selectionProcess.selectionLevelId < selId && selectionProcess.status !== "Neuspešno") {
dispatch( dispatch(
setDoneProcessReq({ setDoneProcessReq({
id: selectionProcess.id, id: selectionProcess.id,

+ 44
- 12
src/components/Selection/SelectionCard.js Ver fichero

import React, { useContext, useState } from "react";
import React, { useContext, useEffect, useState } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { formatDateSrb, formatTimeSrb } from "../../util/helpers/dateHelpers"; import { formatDateSrb, formatTimeSrb } from "../../util/helpers/dateHelpers";
import { Button, FormControl, InputLabel, MenuItem, Select } from "@mui/material";
import {
Button,
FormControl,
InputLabel,
MenuItem,
Select,
} from "@mui/material";
import { SelectionContext } from "../../context/SelectionContext"; import { SelectionContext } from "../../context/SelectionContext";
import { useDispatch } from "react-redux";
import { useDispatch, useSelector } from "react-redux";
import { import {
setDoneProcessReq, setDoneProcessReq,
setUpdateStatusReq, setUpdateStatusReq,
} from "../../store/actions/processes/processAction"; } from "../../store/actions/processes/processAction";
// import Button from "../Button/Button"; // import Button from "../Button/Button";


const options = ["Zakazan", "Odrađen", "Čeka na zakazivanje"];
const options = ["Zakazan", "Odrađen", "Čeka na zakazivanje", "Neuspešno"];


const SelectionCard = (props) => { const SelectionCard = (props) => {
const [showForm, setShowForm] = useState(false); const [showForm, setShowForm] = useState(false);
const [selected, setSelected] = useState(props.item.status); const [selected, setSelected] = useState(props.item.status);


const { setActiveProcess, setActiveInterview } = useContext(SelectionContext);
const { success } = useSelector((s) => s.statusUpdate);
const {
setActiveProcess,
setActiveInterview,
setActiveProcessUnsuccess,
// activeProcessUnsuccess,
} = useContext(SelectionContext);
const dispatch = useDispatch(); const dispatch = useDispatch();


const statusChange = (e) => { const statusChange = (e) => {
} }
}; };


useEffect(() => {
setShowForm(false);
}, [success]);

const select = (e) => { const select = (e) => {
e.stopPropagation(); e.stopPropagation();
if (e.target.value === "Zakazan") { if (e.target.value === "Zakazan") {
// setovanje context state-a
setActiveProcess({ process: props.item, status: "Zakazan" }); setActiveProcess({ process: props.item, status: "Zakazan" });
}
// poseban blok u slucaju da treba prikazati odredjeni modal kada je izabrano 'NEUSPESNO'
else if (e.target.value === "Neuspešno") {
setActiveProcessUnsuccess(props.item);
} else if (e.target.value === "Odrađen") { } else if (e.target.value === "Odrađen") {
// ukoliko nije zadnji nivo selekcije kreirati proces za sledeci nivo
// u suprotnom samo promeniti status u odradjeno
if (props.item.selectionLevelId !== 4) if (props.item.selectionLevelId !== 4)
dispatch( dispatch(
setDoneProcessReq({ setDoneProcessReq({
dispatch( dispatch(
setUpdateStatusReq({ setUpdateStatusReq({
data: { data: {
// schedulerId: selected,
// appointment: value,
newStatus: "Odrađen", newStatus: "Odrađen",
processId: props.item.id, processId: props.item.id,
}, },


const changeInterviewerHandler = (e) => { const changeInterviewerHandler = (e) => {
e.stopPropagation(); e.stopPropagation();
setActiveInterview(props.item)
setActiveInterview(props.item);
}; };


return ( return (
</FormControl> </FormControl>
</form> </form>
) : ( ) : (
<div className="status" onClick={(e) => statusChange(e)}>
<button>{props.item.status}</button>
<div
className="status"
onClick={(e) => {
if (props.item.status !== "Neuspešno") statusChange(e);
}}
>
<button
className={props.item.status === "Neuspešno" ? "unsucc" : ""}
>
{props.item.status}
</button>
</div> </div>
)} )}
<div className="date-name"> <div className="date-name">
</div> </div>
</div> </div>
</div> </div>
{props.item.scheduler ? (
{props.item.scheduler &&
props.item.status !== "Neuspešno" &&
props.item.status !== "Odrađen" ? (
<div className="sel-item-scheduler"> <div className="sel-item-scheduler">
<div className="change-interbtn"> <div className="change-interbtn">
<Button <Button
className="interbtn"
className="interbtn"
onClick={(e) => changeInterviewerHandler(e)} onClick={(e) => changeInterviewerHandler(e)}
> >
Promeni Promeni

+ 10
- 2
src/context/SelectionContext.js Ver fichero



export const SelectionContext = createContext(); export const SelectionContext = createContext();


export const SelectionProvider = ({children}) => {
export const SelectionProvider = ({ children }) => {
const [activeProcess, setActiveProcess] = useState(null); const [activeProcess, setActiveProcess] = useState(null);
const [activeProcessUnsuccess, setActiveProcessUnsuccess] = useState(null);
const [activeInterview, setActiveInterview] = useState(null); const [activeInterview, setActiveInterview] = useState(null);


return ( return (
<SelectionContext.Provider <SelectionContext.Provider
value={{ activeProcess, setActiveProcess, activeInterview, setActiveInterview }}
value={{
activeProcess,
setActiveProcess,
activeInterview,
setActiveInterview,
setActiveProcessUnsuccess,
activeProcessUnsuccess,
}}
> >
{children} {children}
</SelectionContext.Provider> </SelectionContext.Provider>

+ 63
- 37
src/pages/SelectionProcessPage/SelectionProcessOfApplicantPage.js Ver fichero

import { useTheme } from "@emotion/react"; import { useTheme } from "@emotion/react";
import { useMediaQuery } from "@mui/material"; import { useMediaQuery } from "@mui/material";
import ApplicantSelection from "../../components/Selection/ApplicantSelection"; import ApplicantSelection from "../../components/Selection/ApplicantSelection";
import { setProcessesReq } from "../../store/actions/processes/processesAction"
import { selectProcesses } from "../../store/selectors/processesSelectors"
import { selectApplicant } from "../../store/selectors/applicantWithProcessSelector"
import { setApplicantReq } from "../../store/actions/processes/applicantAction"
import { setProcessesReq } from "../../store/actions/processes/processesAction";
import { selectProcesses } from "../../store/selectors/processesSelectors";
import { selectApplicant } from "../../store/selectors/applicantWithProcessSelector";
import { setApplicantReq } from "../../store/actions/processes/applicantAction";
import parse from "html-react-parser";


const SelectionProcessOfApplicantPage = () => { const SelectionProcessOfApplicantPage = () => {
const theme = useTheme(); const theme = useTheme();
const matches = useMediaQuery(theme.breakpoints.down("sm")); const matches = useMediaQuery(theme.breakpoints.down("sm"));
const applicant = useSelector(selectApplicant)
const applicant = useSelector(selectApplicant);
const processes = applicant?.selectionProcesses; const processes = applicant?.selectionProcesses;
const levels = useSelector(selectProcesses)
const levels = useSelector(selectProcesses);
const { id } = useParams(); const { id } = useParams();
const activeAdsSliderRef = useRef(); const activeAdsSliderRef = useRef();
const { t } = useTranslation(); const { t } = useTranslation();
dispatch(setProcessesReq()); dispatch(setProcessesReq());
}, []); }, []);



var settings = { var settings = {
dots: false, dots: false,
infinite: false, infinite: false,
activeAdsSliderRef.current.slickNext(); activeAdsSliderRef.current.slickNext();
}; };



const concatLevels = () => { const concatLevels = () => {
const applicantSelections = []; const applicantSelections = [];


for (var i = processes.length; i < levels.length; i++) { 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
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={""} />);
applicantSelections.push(
<ApplicantSelection key={i} className="hiddenAd" date={""} />
);
return applicantSelections; return applicantSelections;
}; };


console.log(processes);
console.log(applicant?.selectionProcesses);

return ( return (
<> <>
<div className="l-t-rectangle"></div> <div className="l-t-rectangle"></div>
{processes && processes.length > 0 && ( {processes && processes.length > 0 && (
<div className="active-ads"> <div className="active-ads">
<div className="active-ads-header"> <div className="active-ads-header">
<h1>{t("selection.title")}
<span className="level-header-spliter">
|
</span>
<h1>
{t("selection.title")}
<span className="level-header-spliter">|</span>
<span className="level-header-subheader"> <span className="level-header-subheader">
{applicant.firstName + " " + applicant.lastName} {applicant.firstName + " " + applicant.lastName}
</span> </span>
</h1> </h1>
</div> </div>

<div className="active-ads-ads"> <div className="active-ads-ads">
<div className="active-ads-ads-a"> <div className="active-ads-ads-a">
{!matches && ( {!matches && (
ref={activeAdsSliderRef} ref={activeAdsSliderRef}
> >
{processes.map((process, index) => { {processes.map((process, index) => {
return <ApplicantSelection
levelNumber={index + 1}
levelName={process.selectionLevel.name}
schedguler={`${process?.scheduler?.firstName} ${process?.scheduler?.lastName}`}
link={process.link}
date={new Date(process.date)}
status={process.status}
id={id}
key={index}
className={index === processes.length - 1 ? "active-process" : ""}
/>
return (
<ApplicantSelection
levelNumber={index + 1}
levelName={process.selectionLevel.name}
schedguler={
process?.scheduler
? `${process?.scheduler?.firstName} ${process?.scheduler?.lastName}`
: "Proces nema intervjuera."
}
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()} {processes.length <= levels.length && concatLevels()}
</Slider> </Slider>
)} )}
<div className="active-process-tip"> <div className="active-process-tip">
<h3> <h3>
{t("selection.tipHeader")}
{applicant?.selectionProcesses?.some(
(n) => n.status === "Neuspešno"
)
? "Komentar:"
: t("selection.tipHeader")}
</h3> </h3>
<p> <p>
{t("selection.tipBody")}
{/* {processes?.some(n => n.status === "Neuspešno") && parse(processes?.comment)} */}
{applicant?.selectionProcesses?.some(
(n) => n.status === "Neuspešno"
)
? parse(
applicant?.selectionProcesses.find(
(n) => n.status === "Neuspešno"
).comment
)
: t("selection.tipBody")}
{/* {t("selection.tipBody")} */}
</p> </p>
</div> </div>
</div> </div>
); );
}; };




export default SelectionProcessOfApplicantPage; export default SelectionProcessOfApplicantPage;

+ 14
- 0
src/pages/SelectionProcessPage/SelectionProcessPage.js Ver fichero

import SelectionFilter from "../../components/Selection/SelectionFilter"; import SelectionFilter from "../../components/Selection/SelectionFilter";
import InterviewDialog from "../../components/MUI/InterviewDialog"; import InterviewDialog from "../../components/MUI/InterviewDialog";
import plus from "../../assets/images/plus.png"; import plus from "../../assets/images/plus.png";
import forbiden from "../../assets/images/forbiden.png";
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns"; import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
import { LocalizationProvider } from "@mui/x-date-pickers"; import { LocalizationProvider } from "@mui/x-date-pickers";
import { fetchCandidateOptions } from "../../store/actions/candidates/candidatesActions"; import { fetchCandidateOptions } from "../../store/actions/candidates/candidatesActions";
import { setUsersReq } from "../../store/actions/users/usersActions"; import { setUsersReq } from "../../store/actions/users/usersActions";
import StatusDialog from "../../components/MUI/StatusDialog"; import StatusDialog from "../../components/MUI/StatusDialog";
import InterviewerDialog from "../../components/MUI/InterviewerDialog"; import InterviewerDialog from "../../components/MUI/InterviewerDialog";
import CommentProcessDialog from "../../components/MUI/CommentProcessDialog";


const SelectionProcessPage = ({ history }) => { const SelectionProcessPage = ({ history }) => {
const [toggleFiltersDrawer, setToggleFiltersDrawer] = useState(false); const [toggleFiltersDrawer, setToggleFiltersDrawer] = useState(false);
setActiveProcess, setActiveProcess,
activeInterview, activeInterview,
setActiveInterview, setActiveInterview,
setActiveProcessUnsuccess,
activeProcessUnsuccess,
} = useContext(SelectionContext); } = useContext(SelectionContext);


useEffect(() => { useEffect(() => {
const s = [ const s = [
{ isChecked: false, name: "Zakazan" }, { isChecked: false, name: "Zakazan" },
{ isChecked: false, name: "Odrađen" }, { isChecked: false, name: "Odrađen" },
{ isChecked: false, name: "Neuspešno" },
{ isChecked: false, name: "Čeka na zakazivanje" }, { isChecked: false, name: "Čeka na zakazivanje" },
{ isChecked: false, name: "Čeka se odgovor" }, { isChecked: false, name: "Čeka se odgovor" },
]; ];
setActiveProcess(null); setActiveProcess(null);
}} }}
/> />
<CommentProcessDialog
open={activeProcessUnsuccess !== null}
title={"Komentar"}
subtitle={"Selekcija"}
imgSrc={forbiden}
onClose={() => {
setActiveProcessUnsuccess(null);
}}
/>
<InterviewerDialog <InterviewerDialog
open={activeInterview !== null} open={activeInterview !== null}
title={"Promena intervjuera"} title={"Promena intervjuera"}

Cargando…
Cancelar
Guardar