Просмотр исходного кода

Added ability to move applicant from one selection to other

pull/41/head
Safet Purkovic 3 лет назад
Родитель
Сommit
327544aec7

+ 73
- 230
src/assets/styles/components/_selectionProcessPage.scss Просмотреть файл

@@ -1,78 +1,59 @@
h1,
h3 {
margin: 0;
padding: 0;
}

.ads {
.selections {
margin-top: 36px;
padding-left: 3rem;
padding-left: 72px;
}

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

.activee{
/* Blue 4 */

background : #E8F7FF;
border : 1px solid #226CB0;
}

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

.active-ads-subheader {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-size: 24px;
line-height: 36px;
padding-left: 0.3rem;
/* identical to box height, or 100% */
color:#226CB0;
letter-spacing: 0.02em;
}
.active-ads-subheader-spliter {
.level-header-subheader {
font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-size: 24px;
line-height: 36px;
padding-left: 0.3rem;
/* identical to box height, or 100% */
color:#272727;
letter-spacing: 0.02em;
font-style: normal;
font-weight: 600;
font-size: 24px;
line-height: 36px;
padding-left: 0.3rem;
color: #226CB0;
letter-spacing: 0.02em;
}

.filter-vector {
margin-left: 0.5rem !important;
.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;
}

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

.active-ads-ads-ad {
.selection-levels-processes-process {
padding-left: 81px;
display: flex;
}


.selection-card {
display: flex;
flex-direction: column;
justify-content: start;
align-items: left;
// width: 550px;
height: fit-content;
padding: 36px;
background: #F4F4F4;
@@ -82,10 +63,14 @@ letter-spacing: 0.02em;
margin-right: 36px;
}

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

.grey {
color: #e4e4e4
}

.selection-item {
display: flex;
flex-direction: row;
@@ -93,7 +78,6 @@ letter-spacing: 0.02em;
vertical-align: top;
align-items: left;
width: 400px;
// height: 400px;
padding: 18px 36px;
background: #FFFFFF;
border: 1px solid #e4e4e4;
@@ -128,41 +112,22 @@ letter-spacing: 0.02em;
flex-grow: 0;
}

.ad-card-logo img {
width: 61px;
height: 49px;
flex: none;
order: 2;
flex-grow: 0;
}
.selection-item-name, .selection-item-date{
margin: auto 0 !important;
.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;
}

.ad-card-buttons {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: center;
padding: 0px;
gap: 18px;
width: 281px;
height: 38px;
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: 0;
order: 2;
flex-grow: 0;
}

@@ -184,176 +149,53 @@ flex-grow: 0;
flex-grow: 0;
}

.add-ad {
margin-top: 49px;
display: flex;
justify-content: flex-end;
align-items: center;
padding-right: 5rem !important;
padding-bottom: 49px;
}

.add-ad-btn {
.sel-item {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
padding: 18px 72px;
gap: 10px;
width: 201px;
height: 51px;
background: #226cb0;
border-radius: 9px;
}

.ad-filters-header-container {
display: flex;
justify-content: space-between;
}

.ad-filters-header {
display: flex;
align-items: center;
}

.ad-filters-header-close {
cursor: pointer;
}

.ad-filters-header > * {
margin-right: 0.25rem;
}

.ad-filters-header img {
width: 18px;
height: 15.75px;
}

.ad-filters-header sub {
color: #226cb0;
padding: 18px 36px;
gap: 18px;
width: 458px;
background: #FFFFFF;
border: 1px solid #E4E4E4;
border-radius: 18px;
}

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

.ad-filters-experience {
margin-top: 18px;
box-sizing: border-box;
}

.ad-filters-experience-slider {
margin-top: 5px;
}

.ad-filters-technologies {
margin-top: 18px;
}

.ad-filters-employment-type {
display: flex;
}

.ad-filters-employment-type > button {
margin-right: 0.5rem;
margin-top: 18px;
}

.ad-filters-search {
margin-top: 18px;
padding-bottom: 18px;
}

.ad-filters-search > * {
width: 100%;
}

.sel-item{
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 18px 36px;
gap: 18px;

width: 458px;

/* White */

background: #FFFFFF;
/* Gray E4 */

border: 1px solid #E4E4E4;
border-radius: 18px;
}

.sel-item .p{
/* Paragraph */

font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
font-size: 16px;
line-height: 20px;

/* Main Black */
color: #272727;


/* Inside auto layout */

flex: none;
order: 0;
flex-grow: 0;
}

.sel-item .date{
/* 22.07. | 14:10h */

/* Paragraph */

font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 400;
font-size: 16px;
line-height: 20px;

/* Main Black */

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

.sel-item .rig{
height: 20px;

/* Bold Paragraph */

font-family: 'Source Sans Pro';
font-style: normal;
font-weight: 600;
font-size: 16px;
line-height: 20px;
text-align: right;

/* Main Blue */

color: #226CB0;


/* Inside auto layout */

flex: none;
order: 1;
flex-grow: 0;
.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 .p button {
.sel-item .status button {
box-sizing: border-box;
display: flex;
flex-direction: row;
@@ -365,6 +207,7 @@ flex-grow: 0;
min-width: 76px;
height: 38px;
border: 1px solid #e4e4e4;
background: white;
border-radius: 9px;
flex: none;
order: 0;

+ 47
- 25
src/components/Selection/Selection.js Просмотреть файл

@@ -1,37 +1,50 @@
import React from "react";
import PropTypes from "prop-types";
import { selectDoneProcessError } from "../../store/selectors/processesSelectors";
import { setDoneProcessReq } from "../../store/actions/processes/processesAction";
import { useDispatch, useSelector } from "react-redux";
import { formatDateSrb, formatTimeSrb } from "../../util/helpers/dateHelpers";


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

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

const dropItem = (e,selId) =>{
var data = e.dataTransfer.getData("text/plain");
const applicant = JSON.parse(data);
if(applicant.currentSelection !== selId){
// SEND REQUEST TO BACKEND TO STORE NEW SELECTION
console.log('jup')
}
}

const Selection = (props) => {
console.log(props.selection);
const applicants = props.selection.applicants;
const renderList = applicants.map((item, index) => {
const applicants = props.selection.selectionProcesses;
const errorMessage = useSelector(selectDoneProcessError);
const dispatch = useDispatch();

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

const renderList = applicants?.map((item, index) => {
return <div draggable key={index} className="sel-item" onDragStart={e => dragStart(e,item)}>
<div className="p">
<div className="status">
<button>{item.status}</button>
</div>
<div className="date">
<p>{item.date}</p>
<p>{formatDateSrb(item.date)} <span className="grey">|</span> {formatTimeSrb(item.date)}</p>
</div>
<div className="rig">
<p>{item.name}</p>
<div className="full-name">
<p>{item.applicant.firstName + " " + item.applicant.lastName}</p>
</div>
</div>
}
@@ -46,7 +59,12 @@ const Selection = (props) => {
<h3>{props.selection.name}</h3>
</div>

{renderList}
{applicants.length > 0 && renderList}
{applicants.length === 0 && <div className="sel-item">
<div className="date">
<p>Nema kandidata u selekciji</p>
</div>
</div>}
</div>
);
};
@@ -55,13 +73,17 @@ Selection.propTypes = {
selection: PropTypes.shape({
id: PropTypes.number,
name : PropTypes.string,
applicants: PropTypes.arrayOf(PropTypes.shape({
selectionProcesses: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.number,
name: PropTypes.string,
date: PropTypes.string,
status: PropTypes.string,
currentSelection: PropTypes.number,
map: PropTypes.func
map: PropTypes.func,
applicant: PropTypes.shape({
firstName: PropTypes.string,
lastName: PropTypes.string
})
}))
}),
};

+ 29
- 101
src/pages/SelectionProcessPage/SelectionProcessPage.js Просмотреть файл

@@ -1,15 +1,35 @@
import React, { useState } from "react";
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 { setProcessesReq } from "../../store/actions/processes/processesAction";
import { selectProcesses, selectDoneProcess } from "../../store/selectors/processesSelectors";

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

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

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

// console.log(errorMessage);
// console.log(doneErrorMessage);

const handleToggleFiltersDrawer = () => {
setToggleFiltersDrawer((oldState) => !oldState);
@@ -19,100 +39,8 @@ const SelectionProcessPage = () => {
setToggleModal((oldState) => !oldState);
};

const selections = [
{
id: 1,
name: "HR interview",
applicants: [
{
id: 1,
name: "Stefan Petrovic",
status: "Zakazan",
date: "01.01.2022 11:00",
currentSelection: 1
},
{
id: 2,
name: "Stefan Petrovic",
status: "Otkazan",
date: "01.01.2022 11:00",
currentSelection: 1
},
{
id: 3,
name: "Stefan Petrovic",
status: "Ceka na zakazivanje",
currentSelection: 1
}]
},
{
id: 2,
name: "Screening test",
applicants: [
{
id: 1,
name: "Stefan Petrovic",
status: "Zakazan",
date: "01.01.2022 11:00",
currentSelection: 2
},
{
id: 2,
name: "Stefan Petrovic",
status: "Otkazan",
date: "01.01.2022 11:00",
currentSelection: 2
}]
},
{
id: 3,
name: "Technical interview",
applicants: [
{
id: 1,
name: "Stefan Petrovic",
status: "Zakazan",
date: "01.01.2022 11:00",
currentSelection: 3
},
{
id: 2,
name: "Stefan Petrovic",
status: "Otkazan",
date: "01.01.2022 11:00",
currentSelection: 3
},
{
id: 3,
name: "Stefan Petrovic",
status: "Ceka na zakazivanje",
currentSelection: 3
}]
},
{
id: 4,
name: "Final decision",
applicants: [
{
id: 1,
name: "Stefan Petrovic",
status: "Zakazan",
date: "01.01.2022 11:00",
currentSelection: 4
},
{
id: 2,
name: "Stefan Petrovic",
status: "Otkazan",
date: "01.01.2022 11:00",
currentSelection: 4
}]
}
]


const renderList = selections.map((item, index) => {
const renderList = processes.map((item, index) => {
return <Selection selection={item} key={index}/>
}
);
@@ -127,14 +55,14 @@ const SelectionProcessPage = () => {
handleClose={handleToggleFiltersDrawer}
/>
<AddAdModal open={toggleModal} handleClose={handleToggleModal} />
<div className="ads">
<div className="active-ads">
<div className="active-ads-header">
<div className="selections">
<div className="selection-levels">
<div className="level-header">
<h1>{t("selection.title")}
<span className="active-ads-subheader-spliter">
<span className="level-header-spliter">
|
</span>
<span className="active-ads-subheader">
<span className="level-header-subheader">
Svi kandidati
</span>
</h1>
@@ -147,8 +75,8 @@ const SelectionProcessPage = () => {
<img src={filterVector} alt="filter" className="filter-vector" />
</IconButton>
</div>
<div className="active-ads-ads">
<div className="active-ads-ads-ad">
<div className="selection-levels-processes">
<div className="selection-levels-processes-process">
{renderList}
</div>
</div>

+ 6
- 1
src/request/apiEndpoints.js Просмотреть файл

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

+ 5
- 0
src/request/processesReguest.js Просмотреть файл

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

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

+ 37
- 0
src/store/actions/processes/processesAction.js Просмотреть файл

@@ -0,0 +1,37 @@
import {
FETCH_PROCESSES_REQ,
FETCH_PROCESSES_ERR,
FETCH_PROCESSES_SUCCESS,
PUT_PROCESS_ERR,
PUT_PROCESS_REQ,
PUT_PROCESS_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,
});

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
});

+ 6
- 0
src/store/actions/processes/processesActionConstants.js Просмотреть файл

@@ -0,0 +1,6 @@
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';

+ 5
- 0
src/store/reducers/index.js Просмотреть файл

@@ -9,6 +9,9 @@ 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 candidatesReducer from './candidates/candidatesReducer';
import processesReducer from './processes/processesReducer';

export default combineReducers({
login: loginReducer,
@@ -21,4 +24,6 @@ export default combineReducers({
ads: adsReducer,
ad: adReducer,
archiveAds: archiveAdsReducer,
candidates: candidatesReducer,
processes: processesReducer
});

+ 51
- 0
src/store/reducers/processes/processesReducer.js Просмотреть файл

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

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

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

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

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

function setStateDoneProcess(state, action) {
return {
...state,
doneProcess: action.payload,
};
}

function setDoneProcessErrorMessage(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(),
]);
}

+ 28
- 0
src/store/saga/processSaga.js Просмотреть файл

@@ -0,0 +1,28 @@
import { all, call, put, takeLatest } from "redux-saga/effects";
import { getAllLevels, doneProcess } from "../../request/processesReguest";
import { setProcesses, setProcessesError, setDoneProcess, setDoneProcessError } from "../actions/processes/processesAction";
import { FETCH_PROCESSES_REQ, PUT_PROCESS_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 done = yield call(doneProcess,id);
yield put(setDoneProcess(done));
} catch (error) {
yield put(setDoneProcessError(error));
}
}

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

+ 19
- 0
src/store/selectors/processesSelectors.js Просмотреть файл

@@ -0,0 +1,19 @@
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
);

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

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

export const selectDoneProcessError = createSelector(
doneProcessSelector,
(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.');
}

Загрузка…
Отмена
Сохранить