| @@ -101,16 +101,32 @@ h3 { | |||
| border-radius: 18px; | |||
| gap: 18px; | |||
| margin-right: 36px; | |||
| transition: background-color 0.35s ease; | |||
| @include media-below($bp-xl) { | |||
| margin-right: 20px !important; | |||
| padding: 18px !important; | |||
| } | |||
| &.over{ | |||
| background-color: $mainBlueLight !important; | |||
| transition: background-color 0.35s ease; | |||
| } | |||
| } | |||
| .sel-item:hover { | |||
| transition: .25s; | |||
| scale: 1.05; | |||
| border-color: $mainBlue !important; | |||
| background-color: $mainBlueLight !important; | |||
| border-color:$mainBlue; | |||
| .sel-item-inner{ | |||
| background-color: $mainBlueLight !important; | |||
| transition: .25s; | |||
| .status button{ | |||
| background-color: $mainBlueLight !important; | |||
| transition: .25s; | |||
| } | |||
| } | |||
| } | |||
| .bg-danger { | |||
| @@ -213,7 +229,30 @@ h3 { | |||
| flex-grow: 0; | |||
| } | |||
| .sel-item { | |||
| .sel-item{ | |||
| border-radius: 18px; | |||
| border: 1px solid #e4e4e4; | |||
| overflow: hidden; | |||
| transition: .25s; | |||
| } | |||
| .sel-item-scheduler{ | |||
| display: flex; | |||
| justify-content: space-between; | |||
| background: #E4E4E4; | |||
| p{ | |||
| padding: 12.5px 25px 12.5px 0px !important; | |||
| font-family: 'Source Sans Pro'; | |||
| font-style: italic; | |||
| font-weight: 400; | |||
| font-size: 16px; | |||
| line-height: 20px; | |||
| text-align: right; | |||
| color: #272727; | |||
| } | |||
| } | |||
| .sel-item-inner { | |||
| transition: .25s; | |||
| display: flex; | |||
| flex-direction: row; | |||
| align-items: center; | |||
| @@ -222,17 +261,17 @@ h3 { | |||
| cursor: pointer; | |||
| width: 458px; | |||
| background: #ffffff; | |||
| border: 1px solid #e4e4e4; | |||
| border-radius: 18px; | |||
| transition: 0.3s; | |||
| // transition: 0.3s; | |||
| @include media-below($bp-xl) { | |||
| justify-content: space-between; | |||
| padding: 18px; | |||
| width: 303px; | |||
| } | |||
| .status button{ transition: .25s;} | |||
| } | |||
| .sel-item-no-data { | |||
| .sel-item-inner-no-data { | |||
| display: flex; | |||
| flex-direction: row; | |||
| align-items: center; | |||
| @@ -250,7 +289,7 @@ h3 { | |||
| } | |||
| } | |||
| .sel-item .status { | |||
| .sel-item-inner .status { | |||
| font-family: "Source Sans Pro"; | |||
| font-style: normal; | |||
| font-weight: 400; | |||
| @@ -266,7 +305,7 @@ h3 { | |||
| } | |||
| } | |||
| .sel-item .date-name { | |||
| .sel-item-inner .date-name { | |||
| display: flex; | |||
| flex-direction: row; | |||
| align-items: flex-start; | |||
| @@ -283,7 +322,7 @@ h3 { | |||
| } | |||
| } | |||
| .sel-item .date-name .date { | |||
| .sel-item-inner .date-name .date { | |||
| font-family: "Source Sans Pro"; | |||
| font-style: normal; | |||
| font-weight: 400; | |||
| @@ -311,13 +350,13 @@ h3 { | |||
| } | |||
| .full-name p, | |||
| .sel-item .date-name .date p { | |||
| .sel-item-inner .date-name .date p { | |||
| @include media-below($bp-xl) { | |||
| font-size: 14px !important; | |||
| } | |||
| } | |||
| .sel-item .status button { | |||
| .sel-item-inner .status button { | |||
| box-sizing: border-box; | |||
| display: flex; | |||
| flex-direction: row; | |||
| @@ -33,13 +33,15 @@ const InterviewDialog = ({ | |||
| responsive, | |||
| }) => { | |||
| const [selected, setSelected] = useState(""); | |||
| const [selectedInterviewer, setSelectedInterviewer] = useState(""); | |||
| const [value, setValue] = useState(null); | |||
| const theme = useTheme(); | |||
| const fullScreen = useMediaQuery(theme.breakpoints.down("md")); | |||
| const { options } = useSelector((n) => n.options); | |||
| const { user } = useSelector((s) => s.user); | |||
| const { users } = useSelector((n) => n.users); | |||
| // const { user } = useSelector((s) => s.user); | |||
| const { isSuccess } = useSelector((s) => s.initProcess); | |||
| const dispatch = useDispatch(); | |||
| @@ -49,31 +51,34 @@ const InterviewDialog = ({ | |||
| }, [dispatch, isSuccess]); | |||
| const handleChange = (newValue) => { | |||
| if (isValid(newValue)) { // throws an error if invalid value is set | |||
| if (isValid(newValue)) { | |||
| // throws an error if invalid value is set | |||
| var date = format(newValue, "yyyy-MM-dd'T'HH:mm:ss.SSSxxx"); | |||
| setValue(date); | |||
| } | |||
| }; | |||
| useEffect(()=>{ | |||
| setSelected('') | |||
| setValue(null) | |||
| }, [onClose]) | |||
| useEffect(() => { | |||
| setSelected(""); | |||
| setSelectedInterviewer(""); | |||
| setValue(null); | |||
| }, [onClose]); | |||
| const handleClose = () => { | |||
| onClose(); | |||
| }; | |||
| console.log(selectedInterviewer) | |||
| const submitHandler = () => { | |||
| dispatch( | |||
| fetchInitProcess({ | |||
| model: { | |||
| schedulerId: user.id, | |||
| appointment: value, | |||
| applicantId: selected, | |||
| }, | |||
| }) | |||
| ); | |||
| dispatch( | |||
| fetchInitProcess({ | |||
| model: { | |||
| schedulerId: selectedInterviewer, | |||
| appointment: value, | |||
| applicantId: selected, | |||
| }, | |||
| }) | |||
| ); | |||
| }; | |||
| return ( | |||
| @@ -171,6 +176,32 @@ const InterviewDialog = ({ | |||
| : ""} | |||
| </Select> | |||
| </FormControl> | |||
| <FormControl fullWidth> | |||
| <InputLabel id="demo-simple-select-label"> | |||
| Ime intervjuera (opciono) | |||
| </InputLabel> | |||
| <Select | |||
| labelId="demo-simple-select-label" | |||
| id="demo-simple-select" | |||
| value={selectedInterviewer} | |||
| label="Ime intervjuera (opciono)" | |||
| onChange={(e) => { | |||
| setSelectedInterviewer(e.target.value); | |||
| }} | |||
| > | |||
| {users | |||
| ? users.map(({ id, firstName, lastName }, index) => ( | |||
| <MenuItem | |||
| key={index} | |||
| sx={{ textAlign: "left" }} | |||
| value={id} | |||
| > | |||
| {firstName} {lastName} | |||
| </MenuItem> | |||
| )) | |||
| : ""} | |||
| </Select> | |||
| </FormControl> | |||
| <DateTimePicker | |||
| label="Termin (opciono)" | |||
| value={value} | |||
| @@ -0,0 +1,228 @@ | |||
| import React, { useContext, useState } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import x from "../../assets/images/x.png"; | |||
| import { | |||
| Dialog, | |||
| DialogTitle, | |||
| DialogActions, | |||
| useMediaQuery, | |||
| useTheme, | |||
| DialogContent, | |||
| FormControl, | |||
| InputLabel, | |||
| Select, | |||
| MenuItem, | |||
| 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"; | |||
| const StatusDialog = ({ | |||
| title, | |||
| subtitle, | |||
| imgSrc, | |||
| onClose, | |||
| open, | |||
| maxWidth, | |||
| fullWidth, | |||
| responsive, | |||
| }) => { | |||
| const [selected, setSelected] = useState(""); | |||
| const [value, setValue] = useState(null); | |||
| const { activeProcess, setActiveProcess } = useContext(SelectionContext) | |||
| const theme = useTheme(); | |||
| const fullScreen = useMediaQuery(theme.breakpoints.down("md")); | |||
| const { users } = useSelector((s) => s.users); | |||
| const { isSuccess } = useSelector((s) => s.initProcess); | |||
| const dispatch = useDispatch(); | |||
| useEffect(() => { | |||
| handleClose(); | |||
| }, [dispatch, isSuccess]); | |||
| const handleChange = (newValue) => { | |||
| if (isValid(newValue)) { // throws an error if invalid value is set | |||
| var date = format(newValue, "yyyy-MM-dd'T'HH:mm:ss.SSSxxx"); | |||
| setValue(date); | |||
| } | |||
| }; | |||
| useEffect(()=>{ | |||
| setSelected('') | |||
| setValue(null) | |||
| }, [onClose]) | |||
| const handleClose = () => { | |||
| onClose(); | |||
| }; | |||
| const submitHandler = () => { | |||
| dispatch( | |||
| setUpdateStatusReq({ | |||
| data: { | |||
| schedulerId: selected, | |||
| appointment: value, | |||
| newStatus: activeProcess.status, | |||
| processId: activeProcess.process.id, | |||
| }, | |||
| responseHandler: apiSuccess | |||
| }) | |||
| ); | |||
| }; | |||
| const apiSuccess = () =>{ | |||
| setActiveProcess(null) | |||
| } | |||
| return ( | |||
| <Dialog | |||
| maxWidth={maxWidth} | |||
| keepMounted={false} | |||
| fullWidth={fullWidth} | |||
| fullScreen={responsive && fullScreen} | |||
| onClose={handleClose} | |||
| open={open} | |||
| style={{ | |||
| padding: "36px", | |||
| }} | |||
| > | |||
| <div style={{ padding: "36px" }}> | |||
| <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 interviewDialog"> | |||
| <FormControl fullWidth> | |||
| <InputLabel id="demo-simple-select-label"> | |||
| Ime intervjuera | |||
| </InputLabel> | |||
| <Select | |||
| labelId="demo-simple-select-label" | |||
| id="demo-simple-select" | |||
| value={selected} | |||
| label="Ime intervjuera" | |||
| onChange={(e) => { | |||
| setSelected(e.target.value); | |||
| }} | |||
| > | |||
| {users | |||
| ? users.map( | |||
| ({ id, firstName, lastName }, index) => ( | |||
| <MenuItem | |||
| key={index} | |||
| sx={{ textAlign: "left" }} | |||
| value={id} | |||
| > | |||
| {firstName} {lastName} | |||
| </MenuItem> | |||
| ) | |||
| ) | |||
| : ""} | |||
| </Select> | |||
| </FormControl> | |||
| {/* {activeProcess.process && activeProcess.process.date ? <p>Proces ima zakazan termin</p> : ''} */} | |||
| <DateTimePicker | |||
| label="Termin" | |||
| value={value} | |||
| onChange={handleChange} | |||
| renderInput={(params) => <TextField {...params} />} | |||
| /> | |||
| </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} | |||
| > | |||
| Cancel | |||
| </IconButton> | |||
| ) : ( | |||
| "" | |||
| )} | |||
| <IconButton | |||
| data-testid="editbtn" | |||
| className={`c-btn--primary-outlined sm-full interview-btn c-btn dialog-btn`} | |||
| onClick={submitHandler} | |||
| > | |||
| Confirm | |||
| </IconButton> | |||
| </DialogActions> | |||
| </div> | |||
| </Dialog> | |||
| ); | |||
| }; | |||
| StatusDialog.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 StatusDialog; | |||
| @@ -3,6 +3,7 @@ import PropType from "prop-types"; | |||
| import { useLocation } from "react-router-dom"; | |||
| import Navbar from "../../components/MUI/NavbarComponent"; | |||
| import { FormProvider } from "../../context/FormContext"; | |||
| import { SelectionProvider } from "../../context/SelectionContext"; | |||
| // import AppRoutes from "../../AppRoutes"; | |||
| const urls = [ | |||
| @@ -23,6 +24,13 @@ const MainContainer = ({ children }) => { | |||
| <FormProvider> | |||
| <div className="">{children}</div> | |||
| </FormProvider> | |||
| ) : pathname === "/selectionFlow" ? ( | |||
| <SelectionProvider> | |||
| <div className=""> | |||
| <Navbar /> | |||
| <div className="h-withHeader">{children}</div> | |||
| </div> | |||
| </SelectionProvider> | |||
| ) : ( | |||
| <div className=""> | |||
| <Navbar /> | |||
| @@ -1,34 +1,30 @@ | |||
| import React from "react"; | |||
| import React, { useState } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { selectDoneProcessError } from "../../store/selectors/processSelectors"; | |||
| import { selectAuthUser } from "../../store/selectors/userSelectors"; | |||
| import { setDoneProcessReq } from "../../store/actions/processes/processAction"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { formatDateSrb, formatTimeSrb } from "../../util/helpers/dateHelpers"; | |||
| // import { formatDateSrb, formatTimeSrb } from "../../util/helpers/dateHelpers"; | |||
| import { SELECTION_PROCESS_OF_APPLICANT_PAGE } from "../../constants/pages"; | |||
| import { PUT_PROCESS_LOADING } from "../../store/actions/processes/processesActionConstants"; | |||
| import { selectIsLoadingByActionType } from "../../store/selectors/loadingSelectors"; | |||
| import Backdrop from "../../components/MUI/BackdropComponent"; | |||
| import { IconButton } from "@mui/material"; | |||
| import plus from "../../assets/images/plus.png"; | |||
| const dragStart = (e, applicant) => { | |||
| e.dataTransfer.setData("text/plain", JSON.stringify(applicant)); | |||
| }; | |||
| const dragOver = (e) => { | |||
| e.preventDefault(); | |||
| }; | |||
| import SelectionCard from "./SelectionCard"; | |||
| const Selection = (props) => { | |||
| const [over, setOver] = useState(false); | |||
| const allApplicants = props.selection.selectionProcesses; | |||
| const errorMessage = useSelector(selectDoneProcessError); | |||
| const dispatch = useDispatch(); | |||
| const user = useSelector(selectAuthUser); | |||
| const dropItem = (e, selId) => { | |||
| setOver(false); | |||
| var data = e.dataTransfer.getData("text/plain"); | |||
| const selectionProcess = JSON.parse(data); | |||
| if (selectionProcess.selectionLevelId < selId) { | |||
| dispatch( | |||
| setDoneProcessReq({ | |||
| @@ -39,11 +35,21 @@ const Selection = (props) => { | |||
| }) | |||
| ); | |||
| } | |||
| if (errorMessage) { | |||
| console.log(errorMessage); | |||
| } | |||
| }; | |||
| const dragStart = (e, applicant) => { | |||
| e.dataTransfer.setData("text/plain", JSON.stringify(applicant)); | |||
| }; | |||
| const dragOver = (e) => { | |||
| e.preventDefault(); | |||
| setOver(true); | |||
| }; | |||
| const isLoading = useSelector( | |||
| selectIsLoadingByActionType(PUT_PROCESS_LOADING) | |||
| ); | |||
| @@ -51,33 +57,17 @@ const Selection = (props) => { | |||
| const handleOpenDetails = (id) => { | |||
| props.history.push(SELECTION_PROCESS_OF_APPLICANT_PAGE.replace(":id", id)); | |||
| }; | |||
| const applicants = allApplicants?.filter((a) => a.status !== "Odrađen"); | |||
| const applicants = allApplicants?.filter((a) => a.status !== "Odrađen" || a.selectionLevelId === 4); | |||
| const renderList = applicants?.map((item, index) => { | |||
| return ( | |||
| <div | |||
| draggable | |||
| <SelectionCard | |||
| 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-name"> | |||
| <div className="date"> | |||
| {item.date !== null && item.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> | |||
| </div> | |||
| item={item} | |||
| dragStart={(e) => dragStart(e, item)} | |||
| click={() => handleOpenDetails(item.applicant.applicantId)} | |||
| /> | |||
| ); | |||
| }); | |||
| @@ -86,14 +76,16 @@ const Selection = (props) => { | |||
| data-testid="selection-level" | |||
| dropppable="true" | |||
| id={props.selection.id} | |||
| className="selection-card" | |||
| className={`selection-card ${over ? "over" : ""}`} | |||
| onDragOver={(e) => dragOver(e)} | |||
| onDragLeave={() => setOver(false)} | |||
| onDrop={(e) => dropItem(e, props.selection.id)} | |||
| > | |||
| <div className="selection-card-title"> | |||
| <h3>{props.selection.name}</h3> | |||
| <h3 style={{marginRight:'50px'}}>{props.selection.name}</h3> | |||
| {props.order === 0 ? ( | |||
| <IconButton | |||
| sx={{marginRight:'35px'}} | |||
| className={`c-btn--primary-outlined c-btn td-btn`} | |||
| onClick={props.modalEvent} | |||
| > | |||
| @@ -109,7 +101,6 @@ const Selection = (props) => { | |||
| )} | |||
| </div> | |||
| <Backdrop position="absolute" isLoading={isLoading} /> | |||
| {applicants && | |||
| applicants !== null && | |||
| applicants?.length > 0 && | |||
| @@ -117,7 +108,7 @@ const Selection = (props) => { | |||
| {applicants && applicants !== null && applicants?.length === 0 && ( | |||
| <div className="sel-item-no-data"> | |||
| <div className="date"> | |||
| <p>Nema kandidata u selekciji</p> | |||
| <p style={{width:'240px'}}>Nema kandidata u selekciji</p> | |||
| </div> | |||
| </div> | |||
| )} | |||
| @@ -0,0 +1,147 @@ | |||
| import React, { useContext, useState } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| import { formatDateSrb, formatTimeSrb } from "../../util/helpers/dateHelpers"; | |||
| import { FormControl, InputLabel, MenuItem, Select } from "@mui/material"; | |||
| import { SelectionContext } from "../../context/SelectionContext"; | |||
| import { useDispatch} from "react-redux"; | |||
| import { setDoneProcessReq, setUpdateStatusReq } from "../../store/actions/processes/processAction"; | |||
| const options = ["Zakazan", "Odrađen", "Čeka na zakazivanje"]; | |||
| const SelectionCard = (props) => { | |||
| const [showForm, setShowForm] = useState(false); | |||
| const [selected, setSelected] = useState(props.item.status); | |||
| const { setActiveProcess } = useContext(SelectionContext); | |||
| const dispatch = useDispatch(); | |||
| // const { success } = useSelector(s => s.statusUpdate) | |||
| const statusChange = (e) => { | |||
| if(props.item.status !== "Odrađen"){ | |||
| e.stopPropagation(); | |||
| setShowForm(true); | |||
| } | |||
| }; | |||
| const select = (e) => { | |||
| e.stopPropagation(); | |||
| if (e.target.value === "Zakazan") { | |||
| setActiveProcess({process:props.item, status:'Zakazan'}); | |||
| } else if (e.target.value === "Odrađen") { | |||
| if (props.item.selectionLevelId !== 4) | |||
| dispatch( | |||
| setDoneProcessReq({ | |||
| id: props.item.id, | |||
| name: "Some random name", | |||
| applicantId: props.item.applicant.applicantId | |||
| }) | |||
| ); | |||
| else { | |||
| // pozvati nasu custom metodu za promenu statusa bez prebacivanja u veci nivo | |||
| // promeni status u odradjeno | |||
| dispatch( | |||
| setUpdateStatusReq({ | |||
| data: { | |||
| // schedulerId: selected, | |||
| // appointment: value, | |||
| newStatus: "Odrađen", | |||
| processId: props.item.id, | |||
| } | |||
| })) | |||
| } | |||
| } | |||
| setSelected(e.target.value); | |||
| }; | |||
| const clickHandler = () => { | |||
| if (showForm) { | |||
| setSelected(props.item.status); | |||
| setShowForm(false); | |||
| } else props.click(); | |||
| }; | |||
| // useEffect(()=>{ | |||
| // setShowForm(false) | |||
| // },[success]) | |||
| return ( | |||
| <div | |||
| draggable | |||
| className="sel-item" | |||
| onDragStart={props.dragStart} | |||
| onClick={clickHandler} | |||
| > | |||
| {" "} | |||
| <div | |||
| className={`sel-item-inner ${props.item.scheduler && "withScheduler"}`} | |||
| > | |||
| {showForm ? ( | |||
| <form> | |||
| <FormControl> | |||
| <InputLabel id="demo-simple-select-label">Status</InputLabel> | |||
| <Select | |||
| label="Status" | |||
| onChange={(e) => { | |||
| select(e); | |||
| }} | |||
| onClick={(e) => e.stopPropagation()} | |||
| value={selected} | |||
| sx={{ | |||
| height: "40px", | |||
| fontSize: "14px", | |||
| paddingRight: "5px", | |||
| }} | |||
| > | |||
| {options.map((n, index) => ( | |||
| <MenuItem key={index} sx={{ textAlign: "left" }} value={n}> | |||
| {n} | |||
| </MenuItem> | |||
| ))} | |||
| </Select> | |||
| </FormControl> | |||
| </form> | |||
| ) : ( | |||
| <div className="status" onClick={(e) => statusChange(e)}> | |||
| <button>{props.item.status}</button> | |||
| </div> | |||
| )} | |||
| <div className="date-name"> | |||
| <div className="date"> | |||
| {props.item.date !== null && props.item.date !== "" && ( | |||
| <p> | |||
| {formatDateSrb(props.item.date)} <span className="grey">|</span>{" "} | |||
| {formatTimeSrb(props.item.date)} | |||
| </p> | |||
| )} | |||
| </div> | |||
| <div className="full-name"> | |||
| <p> | |||
| {props.item.applicant.firstName + | |||
| " " + | |||
| props.item.applicant.lastName} | |||
| </p> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| {props.item.scheduler ? ( | |||
| <div className="sel-item-scheduler"> | |||
| <div></div> | |||
| <p> | |||
| Intervjuer: {props.item.scheduler.firstName}{" "} | |||
| {props.item.scheduler.lastName} | |||
| </p> | |||
| </div> | |||
| ) : ( | |||
| "" | |||
| )} | |||
| </div> | |||
| ); | |||
| }; | |||
| SelectionCard.propTypes = { | |||
| item: PropTypes.any, | |||
| click: PropTypes.func, | |||
| dragStart: PropTypes.func, | |||
| }; | |||
| export default SelectionCard; | |||
| @@ -0,0 +1,20 @@ | |||
| import React, { createContext, useState } from "react"; | |||
| import PropTypes from "prop-types"; | |||
| export const SelectionContext = createContext(); | |||
| export const SelectionProvider = ({children}) => { | |||
| const [activeProcess, setActiveProcess] = useState(null); | |||
| return ( | |||
| <SelectionContext.Provider | |||
| value={{ activeProcess, setActiveProcess }} | |||
| > | |||
| {children} | |||
| </SelectionContext.Provider> | |||
| ); | |||
| }; | |||
| SelectionProvider.propTypes = { | |||
| children: PropTypes.node, | |||
| }; | |||
| @@ -1,4 +1,4 @@ | |||
| import React, { useState, useEffect } from "react"; | |||
| import React, { useState, useEffect, useContext } from "react"; | |||
| import { useSelector } from "react-redux"; | |||
| import Selection from "../../components/Selection/Selection"; | |||
| import FilterButton from "../../components/Button/FilterButton"; | |||
| @@ -18,6 +18,9 @@ import plus from "../../assets/images/plus.png"; | |||
| import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns'; | |||
| import { LocalizationProvider } from '@mui/x-date-pickers'; | |||
| import { fetchCandidateOptions } from "../../store/actions/candidates/candidatesActions"; | |||
| import { SelectionContext } from "../../context/SelectionContext"; | |||
| import { setUsersReq } from "../../store/actions/users/usersActions"; | |||
| import StatusDialog from "../../components/MUI/StatusDialog"; | |||
| const SelectionProcessPage = ({ history }) => { | |||
| const [toggleFiltersDrawer, setToggleFiltersDrawer] = useState(false); | |||
| @@ -31,10 +34,14 @@ const SelectionProcessPage = ({ history }) => { | |||
| const dispatch = useDispatch(); | |||
| const statuses = useSelector(selecStatuses); | |||
| const {isSuccess} = useSelector(s => s.initProcess); | |||
| const {success} = useSelector(s => s.statusUpdate); | |||
| const {activeProcess,setActiveProcess} = useContext(SelectionContext) | |||
| useEffect(() => { | |||
| dispatch(fetchCandidateOptions()); | |||
| dispatch(setProcessesReq()); | |||
| dispatch(setUsersReq()); | |||
| const s = [ | |||
| { isChecked: false, name: "Zakazan" }, | |||
| { isChecked: false, name: "Odrađen" }, | |||
| @@ -42,12 +49,13 @@ const SelectionProcessPage = ({ history }) => { | |||
| { isChecked: false, name: "Čeka se odgovor" }, | |||
| ]; | |||
| dispatch(setStatuses(s)); | |||
| }, [isSuccess]); | |||
| }, [isSuccess,success]); | |||
| useEffect(() => { | |||
| dispatch(setUsersReq()); | |||
| dispatch(setProcessesReq()); | |||
| dispatch(setDoneProcess(false)); | |||
| }, [process.process.doneProcess]); | |||
| }, [process.process.doneProcess, success]); | |||
| const handleToggleFiltersDrawer = () => { | |||
| setToggleFiltersDrawer((oldState) => !oldState); | |||
| @@ -81,6 +89,15 @@ const SelectionProcessPage = ({ history }) => { | |||
| handleClose={handleToggleFiltersDrawer} | |||
| statuses={statuses} | |||
| /> | |||
| <StatusDialog | |||
| open={activeProcess !== null} | |||
| title={"Dodavanje intervjuera"} | |||
| subtitle={'Selekcija'} | |||
| imgSrc={plus} | |||
| onClose={() => { | |||
| setActiveProcess(null); | |||
| }} | |||
| /> | |||
| <InterviewDialog | |||
| open={toggleInitModal} | |||
| title={"Dodavanje kandidata"} | |||
| @@ -41,6 +41,7 @@ export default { | |||
| filteredLevels: base + "/selectionlevels/filtered", | |||
| doneProcess: base + "/selectionprocesses", | |||
| getApplicantProcesses: base + "/applicants/processes", | |||
| changeStatus: base + "/selectionprocesses/status-update" | |||
| // allProcesses: base + "/selectionprocesses", | |||
| }, | |||
| patterns: { | |||
| @@ -3,6 +3,7 @@ import apiEndpoints from "./apiEndpoints"; | |||
| export const getAllLevels = () => getRequest(apiEndpoints.processes.allLevels); | |||
| export const doneProcess = (data) => postRequest(apiEndpoints.processes.doneProcess,data); | |||
| export const updateStatus = (data) => postRequest(apiEndpoints.processes.changeStatus,data); | |||
| export const getProcessesOfApplicant = (id) => getRequest(`${apiEndpoints.processes.getApplicantProcesses}/${id}`); | |||
| export const getAllFilteredProcessesReq = (payload) => { | |||
| let statusesQuery = ""; | |||
| @@ -1,4 +1,7 @@ | |||
| import { | |||
| FETCH_STATUS_CHANGE_ERR, | |||
| FETCH_STATUS_CHANGE_REQ, | |||
| FETCH_STATUS_CHANGE_SUCCESS, | |||
| PUT_PROCESS_ERR, | |||
| PUT_PROCESS_REQ, | |||
| PUT_PROCESS_SUCCESS | |||
| @@ -18,4 +21,19 @@ import { | |||
| type: PUT_PROCESS_SUCCESS, | |||
| payload | |||
| }); | |||
| // | |||
| export const setUpdateStatusReq = (payload) => ({ | |||
| type: FETCH_STATUS_CHANGE_REQ, | |||
| payload | |||
| }); | |||
| export const setUpdateStatusErr = (payload) => ({ | |||
| type: FETCH_STATUS_CHANGE_ERR, | |||
| payload, | |||
| }); | |||
| export const setUpdateStatusSucc = () => ({ | |||
| type: FETCH_STATUS_CHANGE_SUCCESS, | |||
| }); | |||
| @@ -27,4 +27,8 @@ export const FETCH_APPLICANT_PROCESSES_SUCCESS = 'FETCH_APPLICANT_PROCESSES_SUCC | |||
| export const FETCH_STATUSES_REQ = 'FETCH_STATUSES_REQ'; | |||
| export const FETCH_STATUSES_ERR = 'FETCH_STATUSES_ERR'; | |||
| export const FETCH_STATUSES_SUCCESS = 'FETCH_STATUSES_SUCCESS'; | |||
| export const CHANGE_STATUS_ISCHECKED_VALUE = 'CHANGE_STATUS_ISCHECKED_VALUE'; | |||
| export const CHANGE_STATUS_ISCHECKED_VALUE = 'CHANGE_STATUS_ISCHECKED_VALUE'; | |||
| export const FETCH_STATUS_CHANGE_REQ = 'FETCH_STATUS_CHANGE_REQ'; | |||
| export const FETCH_STATUS_CHANGE_ERR = 'FETCH_STATUS_CHANGE_ERR'; | |||
| export const FETCH_STATUS_CHANGE_SUCCESS = 'FETCH_STATUS_CHANGE_SUCCESS'; | |||
| @@ -31,6 +31,7 @@ import registerReducer from "./register/registerReducer"; | |||
| import candidateOptionsReducer from "./candidates/candidateOptionsReducer"; | |||
| import initProcessReducer from "./candidates/initProcessReducer"; | |||
| import applyForAdReducer from "./applicants/applyForAdReducer"; | |||
| import statusUpdateReducer from "./processes/statusUpdateReducer"; | |||
| export default combineReducers({ | |||
| login: loginReducer, | |||
| @@ -65,4 +66,5 @@ export default combineReducers({ | |||
| scheduleAppointment: scheduleAppointmentReducer, | |||
| patternApplicants: patternApplicantsReducer, | |||
| applyForAd: applyForAdReducer, | |||
| statusUpdate: statusUpdateReducer | |||
| }); | |||
| @@ -0,0 +1,32 @@ | |||
| import createReducer from "../../utils/createReducer"; | |||
| import { | |||
| FETCH_STATUS_CHANGE_ERR, | |||
| FETCH_STATUS_CHANGE_REQ, | |||
| FETCH_STATUS_CHANGE_SUCCESS, | |||
| } from "../../actions/processes/processesActionConstants"; | |||
| const initialState = { | |||
| success: false, | |||
| errorMessage: "", | |||
| }; | |||
| export default createReducer( | |||
| { | |||
| [FETCH_STATUS_CHANGE_REQ]: setStateErrorReq, | |||
| [FETCH_STATUS_CHANGE_ERR]: setStateErrorMessage, | |||
| [FETCH_STATUS_CHANGE_SUCCESS]: statusUpdateSuccess, | |||
| }, | |||
| initialState | |||
| ); | |||
| function statusUpdateSuccess(state) { | |||
| return { ...state, success: true }; | |||
| } | |||
| function setStateErrorReq(state) { | |||
| return { ...state, success: false }; | |||
| } | |||
| function setStateErrorMessage(state, action) { | |||
| return { ...state, errorMessage: action.payload }; | |||
| } | |||
| @@ -1,12 +1,12 @@ | |||
| import { all, call, put, takeLatest } from "redux-saga/effects"; | |||
| import { getAllLevels, doneProcess, getProcessesOfApplicant, getAllFilteredProcessesReq } from "../../request/processesReguest"; | |||
| import { getAllLevels, doneProcess, getProcessesOfApplicant, getAllFilteredProcessesReq, updateStatus } from "../../request/processesReguest"; | |||
| import { setProcesses, setProcessesError } from "../actions/processes/processesAction"; | |||
| import { addHeaderToken } from "../../request"; | |||
| import { authScopeStringGetHelper } from "../../util/helpers/authScopeHelpers"; | |||
| import { JWT_TOKEN } from "../../constants/localStorage"; | |||
| import { setDoneProcess, setDoneProcessError } from "../actions/processes/processAction"; | |||
| import { setDoneProcess, setDoneProcessError, setUpdateStatusErr, setUpdateStatusSucc } from "../actions/processes/processAction"; | |||
| import { setApplicant, setApplicantError } from "../actions/processes/applicantAction"; | |||
| import { FETCH_PROCESSES_REQ, FETCH_FILTERED_PROCESSES_REQ ,PUT_PROCESS_REQ, FETCH_APPLICANT_PROCESSES_REQ } from "../actions/processes/processesActionConstants"; | |||
| import { FETCH_PROCESSES_REQ, FETCH_FILTERED_PROCESSES_REQ ,PUT_PROCESS_REQ, FETCH_APPLICANT_PROCESSES_REQ, FETCH_STATUS_CHANGE_REQ } from "../actions/processes/processesActionConstants"; | |||
| import { rejectErrorCodeHelper } from "../../util/helpers/rejectErrorCodeHelper"; | |||
| export function* getProcesses() { | |||
| @@ -66,9 +66,26 @@ export function* getApplicantProcesses(payload) { | |||
| } | |||
| } | |||
| } | |||
| export function* changeStatus({payload}) { | |||
| try { | |||
| const JwtToken = yield call(authScopeStringGetHelper, JWT_TOKEN); | |||
| yield call(addHeaderToken, JwtToken); | |||
| yield call(updateStatus,payload.data); | |||
| yield put(setUpdateStatusSucc()); | |||
| if(payload.responseHandler){ | |||
| yield call(payload.responseHandler) | |||
| } | |||
| } catch (error) { | |||
| if (error.response && error.response.data) { | |||
| const errorMessage = yield call(rejectErrorCodeHelper, error); | |||
| yield put(setUpdateStatusErr(errorMessage)); | |||
| } | |||
| } | |||
| } | |||
| export default function* processesSaga() { | |||
| yield all([takeLatest(FETCH_PROCESSES_REQ, getProcesses)]); | |||
| yield all([takeLatest(FETCH_STATUS_CHANGE_REQ, changeStatus)]); | |||
| yield all([takeLatest(FETCH_FILTERED_PROCESSES_REQ, getFilteredProcesses)]); | |||
| yield all([takeLatest(PUT_PROCESS_REQ, finishProcess)]); | |||
| yield all([takeLatest(FETCH_APPLICANT_PROCESSES_REQ, getApplicantProcesses)]); | |||