| @@ -267,7 +267,8 @@ | |||
| display: flex; | |||
| } | |||
| .pattern-details-card-sub-card-add-email input { | |||
| .pattern-details-card-sub-card-add-email input, | |||
| .pattern-details-card-sub-card-add-email select { | |||
| margin-right: 18px; | |||
| flex: 50; | |||
| box-sizing: border-box; | |||
| @@ -7,24 +7,89 @@ import { useParams } from "react-router-dom"; | |||
| import { useDispatch, useSelector } from "react-redux"; | |||
| import { setPatternReq } from "../../store/actions/pattern/patternActions"; | |||
| import { selectPattern } from "../../store/selectors/patternSelectors"; | |||
| import { | |||
| clearNotSentEmailsArray, | |||
| scheduleAppointmentReq, | |||
| } from "../../store/actions/scheduleAppointment/scheduleAppointmentActions"; | |||
| import { selectNotSentEmails } from "../../store/selectors/scheduleAppointmentSelectors"; | |||
| import { setPatternApplicantsReq } from "../../store/actions/patternApplicants/patternApplicantsActions"; | |||
| import { selectPatternApplicants } from "../../store/selectors/patternApplicants"; | |||
| import { PATTERNS_PAGE } from "../../constants/pages"; | |||
| import { useHistory } from "react-router-dom"; | |||
| const PatternDetailsPage = () => { | |||
| const [emails, setEmails] = useState([]); | |||
| const [email, setEmail] = useState(""); | |||
| const pattern = useSelector(selectPattern); | |||
| const patternApplicants = useSelector(selectPatternApplicants); | |||
| const notSentEmails = useSelector(selectNotSentEmails); | |||
| const [selectedPatternApplicants, setSelectedPatternApplicants] = useState( | |||
| [] | |||
| ); | |||
| const { id } = useParams(); | |||
| const dispatch = useDispatch(); | |||
| const regex = /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/ | |||
| const history = useHistory(); | |||
| const navigateToPatternsPage = () => { | |||
| history.push(PATTERNS_PAGE); | |||
| }; | |||
| useEffect(() => { | |||
| setSelectedPatternApplicants(patternApplicants); | |||
| }, [patternApplicants]); | |||
| useEffect(() => { | |||
| dispatch(setPatternApplicantsReq({ id })); | |||
| }, []); | |||
| useEffect(() => { | |||
| dispatch(setPatternReq({ id })); | |||
| }, []); | |||
| useEffect(() => { | |||
| if (patternApplicants.length > 0) { | |||
| setEmail(patternApplicants[0].email); | |||
| } | |||
| }, [patternApplicants]); | |||
| useEffect(() => { | |||
| const arrOfApplicants = []; | |||
| for (let i = 0; i < patternApplicants.length; i++) { | |||
| let s = 0; | |||
| for (let j = 0; j < emails.length; j++) { | |||
| if (patternApplicants[i].email === emails[j]) s = 1; | |||
| } | |||
| if (s === 0) arrOfApplicants.push(patternApplicants[i]); | |||
| } | |||
| setSelectedPatternApplicants(arrOfApplicants); | |||
| if (arrOfApplicants.length > 0) { | |||
| setEmail(arrOfApplicants[0].email); | |||
| } | |||
| }, [emails]); | |||
| useEffect(() => { | |||
| dispatch(clearNotSentEmailsArray()); | |||
| }, []); | |||
| const addNewEmailHandler = () => { | |||
| if (email.length === 0) return; | |||
| if(selectedPatternApplicants.length === 0) return; | |||
| setEmails((oldState) => [...oldState, email]); | |||
| setEmail(""); | |||
| }; | |||
| const scheduleAppointmentHandler = () => { | |||
| dispatch( | |||
| scheduleAppointmentReq({ | |||
| emails, | |||
| patternId: id, | |||
| handleApiResponseSuccess: navigateToPatternsPage, | |||
| }) | |||
| ); | |||
| }; | |||
| return ( | |||
| <> | |||
| {!pattern && ( | |||
| @@ -51,7 +116,7 @@ const PatternDetailsPage = () => { | |||
| </div> | |||
| <div className="pattern-details-card-sub-card"> | |||
| <div className="pattern-details-card-sub-card-title"> | |||
| <p>Screening test</p> | |||
| <p>{pattern.selectionLevel.name}</p> | |||
| </div> | |||
| <div className="pattern-details-card-sub-card-emails"> | |||
| {emails && | |||
| @@ -68,39 +133,34 @@ const PatternDetailsPage = () => { | |||
| <div className="pattern-details-card-sub-card"> | |||
| <div className="pattern-details-card-sub-card-title"> | |||
| <p>Screening test</p> | |||
| <p>{pattern.selectionLevel.name}</p> | |||
| </div> | |||
| <div className="pattern-details-card-sub-card-add-email"> | |||
| <input | |||
| type="text" | |||
| onChange={(e) => setEmail(e.target.value)} | |||
| value={email} | |||
| placeholder="ex. petar.petrovic@mail.com" | |||
| /> | |||
| <button | |||
| onClick={addNewEmailHandler} | |||
| disabled={!regex.test(email)} | |||
| > | |||
| <select onChange={(e) => setEmail(e.target.value)}> | |||
| {selectedPatternApplicants.length > 0 && | |||
| selectedPatternApplicants.map((applicant) => ( | |||
| <option | |||
| key={applicant.applicantId} | |||
| value={applicant.email} | |||
| > | |||
| {applicant.firstName + " " + applicant.lastName} | |||
| </option> | |||
| ))} | |||
| </select> | |||
| <button onClick={addNewEmailHandler}> | |||
| <img src={plusIcon} alt="plus" /> | |||
| </button> | |||
| </div> | |||
| </div> | |||
| {notSentEmails && notSentEmails.map((x) => <p key={x}>{x}</p>)} | |||
| <div className="pattern-details-card-sub-card"> | |||
| <div className="pattern-details-card-sub-card-title"> | |||
| <p>Teskt poruke</p> | |||
| </div> | |||
| <div className="pattern-details-card-sub-card-message-pattern"> | |||
| <textarea | |||
| disabled | |||
| // value={`Postovani, | |||
| // Ovom prilikom Vas obavestavamo da je datum Screening testa zakazan za [selected Date] | |||
| // Srdacan pozdrav, | |||
| // Diligent HR Team`} | |||
| value={pattern.message} | |||
| ></textarea> | |||
| <textarea disabled value={pattern.message}></textarea> | |||
| </div> | |||
| </div> | |||
| @@ -108,7 +168,11 @@ const PatternDetailsPage = () => { | |||
| <Link className="ad-details-buttons-link" to="/patterns"> | |||
| Nazad na sve šablone | |||
| </Link> | |||
| <IconButton className="c-btn c-btn--primary add-ad-btn"> | |||
| <IconButton | |||
| disabled={emails.length === 0} | |||
| className="c-btn c-btn--primary add-ad-btn" | |||
| onClick={scheduleAppointmentHandler} | |||
| > | |||
| <img | |||
| style={{ | |||
| marginRight: "5px", | |||
| @@ -44,9 +44,11 @@ export default { | |||
| patterns: { | |||
| allPatterns: base + "/patterns", | |||
| patternById: base + "/patterns/:id", | |||
| patternApplicantsById: base + "/patterns/corresponding-pattern-applicants/:id", | |||
| filteredPatterns: base + "/patterns/filter", | |||
| createPattern: base + "/patterns", | |||
| updatePattern: base + "/patterns/:id", | |||
| scheduleAppointment: base + "/patterns/schedule-interview" | |||
| }, | |||
| stats: { | |||
| stats: base + "/stats", | |||
| @@ -5,6 +5,8 @@ export const getAllPatterns = () => | |||
| getRequest(apiEndpoints.patterns.allPatterns); | |||
| export const getPatternById = (id) => | |||
| getRequest(apiEndpoints.patterns.patternById.replace(":id", id)); | |||
| export const getPatternApplicantsById = (id) => | |||
| getRequest(apiEndpoints.patterns.patternApplicantsById.replace(":id", id)); | |||
| export const getFilteredPatterns = (payload) => { | |||
| let selectionLevelsQuery = ""; | |||
| for (let i = 0; i < payload.selectionLevels.length; i++) { | |||
| @@ -13,7 +15,9 @@ export const getFilteredPatterns = (payload) => { | |||
| return getRequest( | |||
| apiEndpoints.patterns.filteredPatterns + | |||
| `?fromDate=${ | |||
| payload.fromDate === null ? "" : new Date(payload.fromDate).toISOString() | |||
| payload.fromDate === null | |||
| ? "" | |||
| : new Date(payload.fromDate).toISOString() | |||
| }&toDate=${ | |||
| payload.toDate === null ? "" : new Date(payload.toDate).toISOString() | |||
| }&${selectionLevelsQuery}` | |||
| @@ -26,3 +30,5 @@ export const updatePatternRequest = (payload) => | |||
| apiEndpoints.patterns.updatePattern.replace(":id", payload.id), | |||
| payload | |||
| ); | |||
| export const scheduleAppointmentRequest = (payload) => | |||
| postRequest(apiEndpoints.patterns.scheduleAppointment, payload); | |||
| @@ -0,0 +1,17 @@ | |||
| import { | |||
| createFetchType, | |||
| createSuccessType, | |||
| createErrorType, | |||
| } from "../actionHelpers"; | |||
| const FETCH_PATTERN_APPLICANTS_SCOPE = "FETCH_PATTERN_APPLICANTS"; | |||
| export const FETCH_PATTERN_APPLICANTS_REQ = createFetchType( | |||
| FETCH_PATTERN_APPLICANTS_SCOPE | |||
| ); | |||
| export const FETCH_PATTERN_APPLICANTS_ERR = createErrorType( | |||
| FETCH_PATTERN_APPLICANTS_SCOPE | |||
| ); | |||
| export const FETCH_PATTERN_APPLICANTS_SUCCESS = createSuccessType( | |||
| FETCH_PATTERN_APPLICANTS_SCOPE | |||
| ); | |||
| @@ -0,0 +1,20 @@ | |||
| import { | |||
| FETCH_PATTERN_APPLICANTS_REQ, | |||
| FETCH_PATTERN_APPLICANTS_ERR, | |||
| FETCH_PATTERN_APPLICANTS_SUCCESS, | |||
| } from "./patternApplicantsActionConstants"; | |||
| export const setPatternApplicantsReq = (payload) => ({ | |||
| type: FETCH_PATTERN_APPLICANTS_REQ, | |||
| payload, | |||
| }); | |||
| export const setPatternApplicantsError = (payload) => ({ | |||
| type: FETCH_PATTERN_APPLICANTS_ERR, | |||
| payload, | |||
| }); | |||
| export const setPatternApplicants = (payload) => ({ | |||
| type: FETCH_PATTERN_APPLICANTS_SUCCESS, | |||
| payload, | |||
| }); | |||
| @@ -0,0 +1,14 @@ | |||
| import { | |||
| createFetchType, | |||
| createSuccessType, | |||
| createErrorType, | |||
| } from "../actionHelpers"; | |||
| const SCHEDULE_APPOINTMENT_SCOPE = "SCHEDULE_APPOINTMENT"; | |||
| export const SCHEDULE_APPOINTMENT_REQ = createFetchType(SCHEDULE_APPOINTMENT_SCOPE); | |||
| export const SCHEDULE_APPOINTMENT_ERR = createErrorType(SCHEDULE_APPOINTMENT_SCOPE); | |||
| export const SCHEDULE_APPOINTMENT_SUCCESS = createSuccessType(SCHEDULE_APPOINTMENT_SCOPE); | |||
| export const CLEAR_NOT_SENT_EMAILS_ARRAY = "CLEAR_NOT_SENT_EMAILS_ARRAY"; | |||
| @@ -0,0 +1,25 @@ | |||
| import { | |||
| SCHEDULE_APPOINTMENT_REQ, | |||
| SCHEDULE_APPOINTMENT_ERR, | |||
| SCHEDULE_APPOINTMENT_SUCCESS, | |||
| CLEAR_NOT_SENT_EMAILS_ARRAY, | |||
| } from "./scheduleAppointmentActionConstants"; | |||
| export const scheduleAppointmentReq = (payload) => ({ | |||
| type: SCHEDULE_APPOINTMENT_REQ, | |||
| payload, | |||
| }); | |||
| export const scheduleAppointmentError = (payload) => ({ | |||
| type: SCHEDULE_APPOINTMENT_ERR, | |||
| payload, | |||
| }); | |||
| export const scheduleAppointment = (payload) => ({ | |||
| type: SCHEDULE_APPOINTMENT_SUCCESS, | |||
| payload, | |||
| }); | |||
| export const clearNotSentEmailsArray = () => ({ | |||
| type: CLEAR_NOT_SENT_EMAILS_ARRAY, | |||
| }); | |||
| @@ -24,6 +24,8 @@ import createPatternReducer from "./pattern/createPatternReducer"; | |||
| import updatePatternReducer from "./pattern/updatePatternReducer"; | |||
| import statsReducer from "./stats/statsReducer"; | |||
| import scheduleReducer from "./schedule/scheduleReducer"; | |||
| import scheduleAppointmentReducer from "./pattern/scheduleAppointmentReducer"; | |||
| import patternApplicantsReducer from "./pattern/patternApplicantsReducer"; | |||
| import archiveActiveAdReducer from "./ad/archiveActiveAdReducer"; | |||
| import registerReducer from "./register/registerReducer"; | |||
| @@ -53,6 +55,8 @@ export default combineReducers({ | |||
| createPattern: createPatternReducer, | |||
| updatePattern: updatePatternReducer, | |||
| stats: statsReducer, | |||
| schedule:scheduleReducer, | |||
| register: registerReducer | |||
| schedule: scheduleReducer, | |||
| register: registerReducer, | |||
| scheduleAppointment: scheduleAppointmentReducer, | |||
| patternApplicants: patternApplicantsReducer, | |||
| }); | |||
| @@ -0,0 +1,26 @@ | |||
| import createReducer from "../../utils/createReducer"; | |||
| import { | |||
| FETCH_PATTERN_APPLICANTS_SUCCESS, | |||
| FETCH_PATTERN_APPLICANTS_ERR, | |||
| } from "../../actions/patternApplicants/patternApplicantsActionConstants"; | |||
| const initialState = { | |||
| patternApplicants: [], | |||
| errorMessage: "", | |||
| }; | |||
| export default createReducer( | |||
| { | |||
| [FETCH_PATTERN_APPLICANTS_SUCCESS]: setStatePatternApplicants, | |||
| [FETCH_PATTERN_APPLICANTS_ERR]: setStateErrorMessage, | |||
| }, | |||
| initialState | |||
| ); | |||
| function setStatePatternApplicants(state, action) { | |||
| return { ...state, patternApplicants: action.payload }; | |||
| } | |||
| function setStateErrorMessage(state, action) { | |||
| return { ...state, errorMessage: action.payload }; | |||
| } | |||
| @@ -0,0 +1,41 @@ | |||
| import { | |||
| SCHEDULE_APPOINTMENT_SUCCESS, | |||
| SCHEDULE_APPOINTMENT_ERR, | |||
| CLEAR_NOT_SENT_EMAILS_ARRAY, | |||
| } from "../../actions/scheduleAppointment/scheduleAppointmentActionConstants"; | |||
| import createReducer from "../../utils/createReducer"; | |||
| const initialState = { | |||
| notSentEmails: null, | |||
| errorMessage: "", | |||
| }; | |||
| export default createReducer( | |||
| { | |||
| [SCHEDULE_APPOINTMENT_SUCCESS]: setStateScheduleAppointment, | |||
| [SCHEDULE_APPOINTMENT_ERR]: setScheduleAppointmentErrorMessage, | |||
| [CLEAR_NOT_SENT_EMAILS_ARRAY]: setNotSentEmailsArrayNull, | |||
| }, | |||
| initialState | |||
| ); | |||
| function setStateScheduleAppointment(state, action) { | |||
| return { | |||
| ...state, | |||
| notSentEmails: action.payload ? [...action.payload.notSentEmails] : null, | |||
| }; | |||
| } | |||
| function setScheduleAppointmentErrorMessage(state, action) { | |||
| return { | |||
| ...state, | |||
| errorMessage: action.payload, | |||
| }; | |||
| } | |||
| function setNotSentEmailsArrayNull(state) { | |||
| return { | |||
| ...state, | |||
| notSentEmails: null, | |||
| }; | |||
| } | |||
| @@ -3,7 +3,9 @@ import { | |||
| createPatternRequest, | |||
| getAllPatterns, | |||
| getFilteredPatterns, | |||
| getPatternApplicantsById, | |||
| getPatternById, | |||
| scheduleAppointmentRequest, | |||
| updatePatternRequest, | |||
| } from "../../request/patternsRequest"; | |||
| import { | |||
| @@ -32,6 +34,10 @@ import { authScopeStringGetHelper } from "../../util/helpers/authScopeHelpers"; | |||
| import { JWT_TOKEN } from "../../constants/localStorage"; | |||
| import { addHeaderToken } from "../../request"; | |||
| import { rejectErrorCodeHelper } from "../../util/helpers/rejectErrorCodeHelper"; | |||
| import { SCHEDULE_APPOINTMENT_REQ } from "../actions/scheduleAppointment/scheduleAppointmentActionConstants"; | |||
| import { scheduleAppointment } from "../actions/scheduleAppointment/scheduleAppointmentActions"; | |||
| import { FETCH_PATTERN_APPLICANTS_REQ } from "../actions/patternApplicants/patternApplicantsActionConstants"; | |||
| import { setPatternApplicants } from "../actions/patternApplicants/patternApplicantsActions"; | |||
| export function* getPatterns() { | |||
| try { | |||
| @@ -51,6 +57,18 @@ export function* getPattern({ payload }) { | |||
| } | |||
| } | |||
| export function* getPatternApplicants({ payload }) { | |||
| try { | |||
| const JwtToken = yield call(authScopeStringGetHelper, JWT_TOKEN); | |||
| yield call(addHeaderToken, JwtToken); | |||
| const result = yield call(getPatternApplicantsById, payload.id); | |||
| yield put(setPatternApplicants(result.data)); | |||
| } catch (error) { | |||
| const errorMessage = yield call(rejectErrorCodeHelper, error); | |||
| yield put(setPatternError(errorMessage)); | |||
| } | |||
| } | |||
| export function* filterPatterns({ payload }) { | |||
| try { | |||
| const JwtToken = yield call(authScopeStringGetHelper, JWT_TOKEN); | |||
| @@ -88,12 +106,29 @@ export function* updatePatternSaga({ payload }) { | |||
| } | |||
| } | |||
| export function* scheduleAppointmentSaga({ payload }) { | |||
| try { | |||
| const JwtToken = yield call(authScopeStringGetHelper, JWT_TOKEN); | |||
| yield call(addHeaderToken, JwtToken); | |||
| const { data } = yield call(scheduleAppointmentRequest, payload); | |||
| yield put(scheduleAppointment(data)); | |||
| if (payload.handleApiResponseSuccess) { | |||
| yield call(payload.handleApiResponseSuccess); | |||
| } | |||
| } catch (error) { | |||
| const errorMessage = yield call(rejectErrorCodeHelper, error); | |||
| yield put(setFilteredPatternsError(errorMessage)); | |||
| } | |||
| } | |||
| export default function* adsSaga() { | |||
| yield all([ | |||
| takeLatest(FETCH_PATTERNS_REQ, getPatterns), | |||
| takeLatest(FETCH_PATTERN_REQ, getPattern), | |||
| takeLatest(FETCH_PATTERN_APPLICANTS_REQ, getPatternApplicants), | |||
| takeLatest(FETCH_FILTERED_PATTERNS_REQ, filterPatterns), | |||
| takeLatest(CREATE_PATTERN_REQ, createPatternSaga), | |||
| takeLatest(UPDATE_PATTERN_REQ, updatePatternSaga), | |||
| takeLatest(SCHEDULE_APPOINTMENT_REQ, scheduleAppointmentSaga), | |||
| ]); | |||
| } | |||
| @@ -0,0 +1,13 @@ | |||
| import { createSelector } from "@reduxjs/toolkit"; | |||
| export const patternApplicantsSelector = (state) => state.patternApplicants; | |||
| export const selectPatternApplicants = createSelector( | |||
| patternApplicantsSelector, | |||
| (state) => state.patternApplicants | |||
| ); | |||
| export const selectPatternApplicantsError = createSelector( | |||
| patternApplicantsSelector, | |||
| (state) => state.errorMessage | |||
| ); | |||
| @@ -0,0 +1,13 @@ | |||
| import { createSelector } from "@reduxjs/toolkit"; | |||
| export const scheduleAppointmentSelector = (state) => state.scheduleAppointment; | |||
| export const selectNotSentEmails = createSelector( | |||
| scheduleAppointmentSelector, | |||
| (state) => state.notSentEmails | |||
| ); | |||
| export const selectScheduleAppointmentError = createSelector( | |||
| scheduleAppointmentSelector, | |||
| (state) => state.errorMessage | |||
| ); | |||