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

Merge branch 'feature/1689_add_new_ad_fe' of Neca/HRCenter into FE_dev

pull/56/head
safet.purkovic 3 лет назад
Родитель
Сommit
ad1fd09e76
29 измененных файлов: 1170 добавлений и 68 удалений
  1. Двоичные данные
      src/assets/images/no_active_ads.png
  2. Двоичные данные
      src/assets/images/plus.png
  3. 155
    1
      src/assets/styles/components/_ads.scss
  4. 103
    17
      src/components/Ads/AdFilters.js
  5. 117
    10
      src/components/Ads/AddAdModal.js
  6. 140
    0
      src/components/Ads/AddAdModalFirstStage.js
  7. 136
    0
      src/components/Ads/AddAdModalSecondStage.js
  8. 89
    0
      src/components/Ads/AddAdModalThirdStage.js
  9. 55
    22
      src/pages/AdsPage/AdsPage.js
  10. 14
    2
      src/request/adsRequest.js
  11. 5
    0
      src/request/apiEndpoints.js
  12. 5
    0
      src/request/technologiesRequest.js
  13. 5
    0
      src/store/actions/addAdTechnologies/addAdTechnologiesActionConstants.js
  14. 26
    0
      src/store/actions/addAdTechnologies/addAdTechnologiesActions.js
  15. 18
    0
      src/store/actions/ads/adsAction.js
  16. 5
    1
      src/store/actions/ads/adsActionConstants.js
  17. 14
    0
      src/store/actions/createAd/createAdActionConstants.js
  18. 21
    0
      src/store/actions/createAd/createAdActions.js
  19. 5
    0
      src/store/actions/technologies/technologiesActionConstants.js
  20. 25
    0
      src/store/actions/technologies/technologiesActions.js
  21. 18
    0
      src/store/reducers/ad/adsReducer.js
  22. 26
    0
      src/store/reducers/ad/createAdReducer.js
  23. 18
    14
      src/store/reducers/index.js
  24. 39
    0
      src/store/reducers/technology/addAddTechnologiesReducer.js
  25. 45
    0
      src/store/reducers/technology/technologiesReducer.js
  26. 35
    1
      src/store/saga/adsSaga.js
  27. 2
    0
      src/store/saga/index.js
  28. 36
    0
      src/store/saga/technologiesSaga.js
  29. 13
    0
      src/store/selectors/technologiesSelectors.js

Двоичные данные
src/assets/images/no_active_ads.png Просмотреть файл


Двоичные данные
src/assets/images/plus.png Просмотреть файл


+ 155
- 1
src/assets/styles/components/_ads.scss Просмотреть файл

@@ -63,6 +63,27 @@ h3 {
border-radius: 9px;
cursor: pointer;
}
.active-ads-ads-no-ads {
height: 75vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
width: 466px;
margin: 0 auto;
@include media-below($bp-xl) {
width: 300px;
}
}

.active-ads-ads-no-ads h1 {
margin-bottom: 18px !important;
}

.active-ads-ads-no-ads p {
margin-bottom: 36px !important;
}

.active-ads-ads-ad {
padding-left: 81px;
@@ -132,6 +153,10 @@ h3 {
}
}

.archive-ads-no-active-ads {
padding-bottom: 136px !important;
}

.archive-ad {
box-sizing: border-box;
display: flex;
@@ -281,7 +306,7 @@ h3 {
background-color: $mainBlueLight !important;
}

.ad-card-buttons-button{
.ad-card-buttons-button {
display: flex;
flex-direction: row;
justify-content: center;
@@ -435,6 +460,11 @@ h3 {
}
}

.add-ad-no-ads {
margin: 0 !important;
padding: 0 !important;
}

.ad-filters-header-container {
display: flex;
justify-content: space-between;
@@ -631,4 +661,128 @@ h3 {

.hiddenAd {
visibility: hidden !important;
}

.add-ad-modal {
padding: 36px !important;
border: none !important;
border-radius: 18px;
min-height: 591px !important;
width: 512px !important;
}

.add-ad-modal-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 18px !important;
}

.add-ad-modal-header img {
width: 18px;
height: 18px;
}

.add-ad-modal-header-title {
display: flex;
align-items: flex-end;
}

.add-ad-modal-header-title > * {
margin-right: 6px;
}

.add-ad-modal-header-title-span {
color: $mainBlue;
}

.add-ad-modal-header-icon button {
background-color: transparent;
border: none;
cursor: pointer;
}

.add-ad-modal-header-icon img {
width: 9px !important;
height: 12px !important;
}

.add-ad-modal-stages {
display: flex;
flex-direction: column;
justify-content: space-between;
min-height: calc(591px - 36px - 36px - 20.8px - 18px) !important;
}

.add-ad-modal-stage {
display: flex;
flex-direction: column;
justify-content: space-between;
min-height: calc(591px - 36px - 36px - 20.8px - 18px) !important;
}

.add-ad-modal-stage-sub-card {
display: flex;
flex-direction: column;
margin-bottom: 9px;
}

.add-ad-modal-stage-sub-card-third {
display: flex;
flex-direction: column;
margin-bottom: 9px;
}

.add-ad-modal-stage-sub-card label,
.add-ad-modal-stage-sub-card-third label {
margin-bottom: 4.5px !important;
}

.add-ad-modal-stage-sub-card-title {
margin-bottom: 9px;
font-weight: bold;
}

.add-ad-modal-stage-sub-card input,
.add-ad-modal-stage-sub-card-third textarea {
padding: 9px;
border-radius: 7px;
border: 1px solid #e4e4e4;
outline: none;
}

.add-ad-modal-stage-sub-card-third textarea {
resize: none;
}

.add-ad-modal-stage-sub-card-checkboxes {
padding: 0 9px !important;
}

.add-ad-modal-stage-sub-card-checkboxes {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
}

.add-ad-modal-stage-sub-card-checkboxes .MuiFormControlLabel-root {
width: calc(100% / 3) !important;
margin: 0 !important;
}

.add-ad-modal-stage-sub-card-buttons {
display: flex;
}

.add-ad-modal-stage-sub-card-buttons button {
margin-right: 9px;
}

.add-ad-modal-actions {
display: flex;
justify-content: space-between;
}

.add-ad-modal-actions button {
padding: 18px 72px !important;
}

+ 103
- 17
src/components/Ads/AdFilters.js Просмотреть файл

@@ -8,12 +8,54 @@ import Checkbox from "@mui/material/Checkbox";
import Slider from "@mui/material/Slider";
import filterIcon from "../../assets/images/filter_vector.png";
import x from "../../assets/images/x.png";
import { changeIsCheckedValue } from "../../store/actions/technologies/technologiesActions";
import { useDispatch } from "react-redux";
import { setFilteredAdsReq } from "../../store/actions/ads/adsAction";

const AdFilters = ({ open, handleClose }) => {
const [value, setValue] = useState([0, 10]);
const AdFilters = ({ open, handleClose, technologies }) => {
const [sliderValue, setSliderValue] = useState([0, 10]);
const [employmentType, setEmploymentType] = useState("Work");
const [workHour, setWorkHour] = useState("FullTime");
const dispatch = useDispatch();

const handleSliderChange = (_, newValue) => {
setValue(newValue);
setSliderValue(newValue);
};

const onSubmitFilters = () => {
const tech = technologies
.filter((tech) => tech.isChecked === true)
.map((tech) => tech.name);

if(tech.length === 0) {
return;
}

dispatch(
setFilteredAdsReq({
minExperience: sliderValue[0],
maxExperience: sliderValue[1],
technologies: tech,
workHour,
employmentType,
})
);

handleClose();
};

const handleCheckboxes = (e) => {
const { value } = e.target;

dispatch(changeIsCheckedValue(value));
};

const employmentTypeHandler = (type) => {
setEmploymentType(type);
};

const workHourHandler = (type) => {
setWorkHour(type);
};

const list = () => (
@@ -48,7 +90,7 @@ const AdFilters = ({ open, handleClose }) => {
<div className="ad-filters-experience-slider">
<Slider
getAriaLabel={() => "Temperature range"}
value={value}
value={sliderValue}
onChange={handleSliderChange}
valueLabelDisplay="auto"
max={20}
@@ -61,14 +103,19 @@ const AdFilters = ({ open, handleClose }) => {
</div>
<div className="ad-filters-technologies-checkboxes">
<FormGroup>
<FormControlLabel control={<Checkbox />} label="HTML/CSS" />
<FormControlLabel control={<Checkbox />} label="Vanilla JS" />
<FormControlLabel control={<Checkbox />} label="React" />
<FormControlLabel control={<Checkbox />} label="Angular" />
<FormControlLabel control={<Checkbox />} label="SQL" />
<FormControlLabel control={<Checkbox />} label="Node.js" />
<FormControlLabel control={<Checkbox />} label="PHP" />
<FormControlLabel control={<Checkbox />} label="Git" />
{technologies.map((technology, index) => (
<FormControlLabel
key={index}
control={
<Checkbox
onChange={handleCheckboxes}
value={technology.name}
checked={technology.isChecked}
/>
}
label={technology.name}
/>
))}
</FormGroup>
</div>
</div>
@@ -77,8 +124,26 @@ const AdFilters = ({ open, handleClose }) => {
<p>Tip zaposlenja</p>
</div>
<div className="ad-filters-employment-type">
<button className="c-btn c-btn--primary-outlined">Intership</button>
<button className="c-btn c-btn--primary">Posao</button>
<button
className={`c-btn ${
employmentType === "Intership"
? "c-btn c-btn--primary"
: "c-btn--primary-outlined"
}`}
onClick={employmentTypeHandler.bind(this, "Intership")}
>
Intership
</button>
<button
className={`c-btn ${
employmentType === "Work"
? "c-btn c-btn--primary"
: "c-btn--primary-outlined"
}`}
onClick={employmentTypeHandler.bind(this, "Work")}
>
Posao
</button>
</div>
</div>
<div className="ad-filters-technologies">
@@ -86,12 +151,32 @@ const AdFilters = ({ open, handleClose }) => {
<p>Radno vreme</p>
</div>
<div className="ad-filters-employment-type">
<button className="c-btn c-btn--primary-outlined">Part-time</button>
<button className="c-btn c-btn--primary-outlined">Full-time</button>
<button
className={`c-btn ${
workHour === "PartTime"
? "c-btn c-btn--primary"
: "c-btn--primary-outlined"
}`}
onClick={workHourHandler.bind(this, "PartTime")}
>
Part-time
</button>
<button
className={`c-btn ${
workHour === "FullTime"
? "c-btn c-btn--primary"
: "c-btn--primary-outlined"
}`}
onClick={workHourHandler.bind(this, "FullTime")}
>
Full-time
</button>
</div>
</div>
<div className="ad-filters-search">
<button className="c-btn c-btn--primary">Pretrazi</button>
<button onClick={onSubmitFilters} className="c-btn c-btn--primary">
Pretrazi
</button>
</div>
</div>
</Box>
@@ -109,6 +194,7 @@ const AdFilters = ({ open, handleClose }) => {
AdFilters.propTypes = {
open: PropType.any,
handleClose: PropType.func,
technologies: PropType.any,
};

export default AdFilters;

+ 117
- 10
src/components/Ads/AddAdModal.js Просмотреть файл

@@ -1,7 +1,15 @@
import React from "react";
import React, { useEffect, useState } from "react";
import PropType from "prop-types";
import Box from "@mui/material/Box";
import Modal from "@mui/material/Modal";
import plus from "../../assets/images/plus.png";
import xIcon from "../../assets/images/x.png";
import AddAdModalFirstStage from "./AddAdModalFirstStage";
import AddAdModalSecondStage from "./AddAdModalSecondStage";
import AddAdModalThirdStage from "./AddAdModalThirdStage";
import { useSelector, useDispatch } from "react-redux";
import { setTechnologiesAddAdReq } from "../../store/actions/addAdTechnologies/addAdTechnologiesActions";
import { setCreateAdReq } from "../../store/actions/createAd/createAdActions";

const style = {
position: "absolute",
@@ -12,12 +20,61 @@ const style = {
bgcolor: "background.paper",
border: "2px solid #000",
boxShadow: 24,
pt: 2,
px: 4,
pb: 3,
};

const AddAdModal = ({ open, handleClose }) => {
const [stage, setStage] = useState(1);
const [title, setTitle] = useState("");
const [employmentType, setEmploymentType] = useState("Work");
const [workHour, setWorkHour] = useState("FullTime");
const [expiredAt, setExpiredAt] = useState(new Date());
const [experience, setExperience] = useState(0);
const [keyResponsibilities, setKeyResponsibilities] = useState("");
const [requirements, setRequirements] = useState("");
const [whatWeOffer, setWhatWeOffer] = useState("");
const technologies = useSelector(
(state) => state.addAdTechnologies.technologies
);
const dispatch = useDispatch();

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

const addAdHandler = () => {
const technologiesIds = technologies
.filter((x) => x.isChecked === true)
.map((x) => x.technologyId);

dispatch(
setCreateAdReq({
title,
minimumExperience: experience,
createdAt: new Date(),
expiredAt,
keyResponsibilities,
requirements,
offer: whatWeOffer,
workHour,
employmentType,
technologiesIds,
})
);
handleClose();
};

const completeFirstStage = () => {
console.log(title, employmentType, workHour, expiredAt);
};

const incrementStageHandler = () => {
setStage((oldState) => oldState + 1);
};

const decrementStageHandler = () => {
setStage((oldState) => oldState - 1);
};

return (
<Modal
open={open}
@@ -25,11 +82,61 @@ const AddAdModal = ({ open, handleClose }) => {
aria-labelledby="parent-modal-title"
aria-describedby="parent-modal-description"
>
<Box sx={{ ...style, width: 400 }}>
<h2 id="parent-modal-title">Text in a modal</h2>
<p id="parent-modal-description">
Duis mollis, est non commodo luctus, nisi erat porttitor ligula.
</p>
<Box sx={{ ...style, width: 512 }} className="add-ad-modal">
<div className="add-ad-modal-header">
<div className="add-ad-modal-header-title">
<img src={plus} alt="plus" />
<h3>Dodavanje</h3>
<h4>
<span className="add-ad-modal-header-title-span">| Oglas</span>
</h4>
<h4></h4>
</div>
<div className="add-ad-modal-header-icon">
<button onClick={handleClose}>
<img src={xIcon} alt="xIcon" />
</button>
</div>
</div>

<div className="add-ad-modal-stages">
{stage === 1 && (
<AddAdModalFirstStage
title={title}
setTitle={setTitle}
employmentType={employmentType}
setEmploymentType={setEmploymentType}
workHour={workHour}
setWorkHour={setWorkHour}
expiredAt={expiredAt}
setExpiredAt={setExpiredAt}
onCompleteFirstStage={completeFirstStage}
onIncrementStage={incrementStageHandler}
onDecrementStage={decrementStageHandler}
/>
)}
{stage === 2 && (
<AddAdModalSecondStage
onIncrementStage={incrementStageHandler}
onDecrementStage={decrementStageHandler}
technologies={technologies}
experience={experience}
setExperience={setExperience}
/>
)}
{stage === 3 && (
<AddAdModalThirdStage
onDecrementStage={decrementStageHandler}
keyResponsibilities={keyResponsibilities}
setKeyResponsibilities={setKeyResponsibilities}
requirements={requirements}
setRequirements={setRequirements}
whatWeOffer={whatWeOffer}
setWhatWeOffer={setWhatWeOffer}
onAddAd={addAdHandler}
/>
)}
</div>
</Box>
</Modal>
);
@@ -37,7 +144,7 @@ const AddAdModal = ({ open, handleClose }) => {

AddAdModal.propTypes = {
open: PropType.any,
handleClose: PropType.func
handleClose: PropType.func,
};

export default AddAdModal;

+ 140
- 0
src/components/Ads/AddAdModalFirstStage.js Просмотреть файл

@@ -0,0 +1,140 @@
import React from "react";
import PropTypes from "prop-types";

const AddAdModalFirstStage = ({
onIncrementStage,
onDecrementStage,
onCompleteFirstStage,
title,
employmentType,
workHour,
setTitle,
setEmploymentType,
setWorkHour,
expiredAt,
setExpiredAt,
}) => {
const completeStageHandler = () => {
if(title.length === 0) {
return;
}

onIncrementStage();
onCompleteFirstStage();
};

const employmentTypeHandler = (type) => {
setEmploymentType(type);
};

const workHourHandler = (type) => {
setWorkHour(type);
};

return (
<div className="add-ad-modal-stage">
<div>
<div className="add-ad-modal-stage-sub-card">
<label>Naslov</label>
<input
type="text"
value={title}
placeholder="ex. Medior React Developer"
onChange={(e) => setTitle(e.target.value)}
/>
</div>

<div className="add-ad-modal-stage-sub-card">
<label>Tip zaposlenja</label>
<div className="add-ad-modal-stage-sub-card-buttons">
<button
className={`c-btn ${
employmentType === "Work"
? "c-btn c-btn--primary"
: "c-btn--primary-outlined"
}`}
onClick={employmentTypeHandler.bind(this, "Work")}
>
Posao
</button>
<button
className={`c-btn ${
employmentType === "Intership"
? "c-btn c-btn--primary"
: "c-btn--primary-outlined"
}`}
onClick={employmentTypeHandler.bind(this, "Intership")}
>
Intership
</button>
</div>
</div>

<div className="add-ad-modal-stage-sub-card">
<label>Radno vreme</label>
<div className="add-ad-modal-stage-sub-card-buttons">
<button
className={`c-btn ${
workHour === "PartTime"
? "c-btn c-btn--primary"
: "c-btn--primary-outlined"
}`}
onClick={workHourHandler.bind(this, "PartTime")}
>
Part-time
</button>
<button
className={`c-btn ${
workHour === "FullTime"
? "c-btn c-btn--primary"
: "c-btn--primary-outlined"
}`}
onClick={workHourHandler.bind(this, "FullTime")}
>
Full-time
</button>
</div>
</div>

<div className="add-ad-modal-stage-sub-card">
<label>Datum isteka oglasa</label>
<input
type="date"
placeholder="ex"
value={expiredAt}
onChange={(e) => setExpiredAt(e.target.value)}
/>
</div>
</div>

<div className="add-ad-modal-actions">
<button
className="c-btn c-btn--primary-outlined"
disabled
onClick={onDecrementStage}
>
NAZAD
</button>
<button className="c-btn c-btn--primary" onClick={completeStageHandler}>
NASTAVI
</button>
</div>
</div>
);
};

AddAdModalFirstStage.propTypes = {
onCompleteFirstStage: PropTypes.func,
onIncrementStage: PropTypes.func,
onDecrementStage: PropTypes.func,
title: PropTypes.string,
employmentType: PropTypes.string,
workHour: PropTypes.string,
setTitle: PropTypes.func,
setEmploymentType: PropTypes.func,
setWorkHour: PropTypes.func,
expiredAt: PropTypes.any,
setExpiredAt: PropTypes.func,
};

export default AddAdModalFirstStage;

+ 136
- 0
src/components/Ads/AddAdModalSecondStage.js Просмотреть файл

@@ -0,0 +1,136 @@
import React from "react";
import PropTypes from "prop-types";
import { Checkbox, FormControlLabel } from "@mui/material";
import { useDispatch } from "react-redux";
import { changeIsCheckedAddAdValue } from "../../store/actions/addAdTechnologies/addAdTechnologiesActions";

const AddAdModalSecondStage = ({
onIncrementStage,
onDecrementStage,
technologies,
experience,
setExperience,
}) => {
const dispatch = useDispatch();
const completeStageHandler = () => {
const checkedTechnologies = technologies.filter(
(x) => x.isChecked === true
);
if (checkedTechnologies.length === 0) {
return;
}

onIncrementStage();
};

const handleCheckboxes = (technologyId) => {
dispatch(changeIsCheckedAddAdValue(technologyId));
};

return (
<div className="add-ad-modal-stages">
<div>
<div className="add-ad-modal-stage-sub-card-title">
<label>Neophodne tehnologije</label>
</div>
<div className="add-ad-modal-stage-sub-card">
<div className="add-ad-modal-stage-sub-card-checkboxes-group">
<label>Front-End</label>
<div className="add-ad-modal-stage-sub-card-checkboxes">
{technologies
.filter((x) => x.technologyType === "Frontend")
.map((x) => (
<FormControlLabel
key={x.technologyId}
control={
<Checkbox
value={x.name}
checked={x.isChecked}
onChange={handleCheckboxes.bind(this, x.technologyId)}
/>
}
label={x.name}
/>
))}
</div>
</div>

<div className="add-ad-modal-stage-sub-card-checkboxes-group">
<label>Back-End</label>
<div className="add-ad-modal-stage-sub-card-checkboxes">
{technologies
.filter((x) => x.technologyType === "Backend")
.map((x) => (
<FormControlLabel
key={x.technologyId}
control={
<Checkbox
value={x.name}
checked={x.isChecked}
onChange={handleCheckboxes.bind(this, x.technologyId)}
/>
}
label={x.name}
/>
))}
</div>
</div>

<div className="add-ad-modal-stage-sub-card-checkboxes-group">
<label>Other</label>
<div className="add-ad-modal-stage-sub-card-checkboxes">
{technologies
.filter((x) => x.technologyType === "Other")
.map((x) => (
<FormControlLabel
key={x.technologyId}
control={
<Checkbox
value={x.name}
checked={x.isChecked}
onChange={handleCheckboxes.bind(this, x.technologyId)}
/>
}
label={x.name}
/>
))}
</div>
</div>
</div>

<div className="add-ad-modal-stage-sub-card">
<label>Godine iskustva</label>
<input
type="number"
placeholder="ex. 3 godine iskustva"
value={experience}
onChange={(e) => setExperience(e.target.value)}
/>
</div>
</div>

<div className="add-ad-modal-actions">
<button
className="c-btn c-btn--primary-outlined"
onClick={onDecrementStage}
>
NAZAD
</button>
<button className="c-btn c-btn--primary" onClick={completeStageHandler}>
NASTAVI
</button>
</div>
</div>
);
};

AddAdModalSecondStage.propTypes = {
onIncrementStage: PropTypes.func,
onDecrementStage: PropTypes.func,
technologies: PropTypes.any,
experience: PropTypes.any,
setExperience: PropTypes.func,
};

export default AddAdModalSecondStage;

+ 89
- 0
src/components/Ads/AddAdModalThirdStage.js Просмотреть файл

@@ -0,0 +1,89 @@
import React from "react";
import PropTypes from "prop-types";

const AddAdModalThirdStage = ({
onDecrementStage,
keyResponsibilities,
setKeyResponsibilities,
requirements,
setRequirements,
whatWeOffer,
setWhatWeOffer,
onAddAd,
}) => {
const completeStageHandler = () => {
if (
keyResponsibilities.length === 0 ||
requirements.length === 0 ||
whatWeOffer.length === 0
) {
return;
}

onAddAd();
};

return (
<div className="add-ad-modal-stage">
<div>
<div className="add-ad-modal-stage-sub-card-third">
<label>Glavna zaduzenja</label>
<textarea
type="text"
placeholder="ex. Working as a React developer on various projects... (Separate every responsibility with | sign)"
value={keyResponsibilities}
onChange={(e) => setKeyResponsibilities(e.target.value)}
rows={5}
></textarea>
</div>

<div className="add-ad-modal-stage-sub-card-third">
<label>Uslovi</label>
<textarea
type="text"
placeholder="ex. Good software development fundamentals... (Separate every condition with | sign)"
value={requirements}
onChange={(e) => setRequirements(e.target.value)}
rows={5}
></textarea>
</div>

<div className="add-ad-modal-stage-sub-card-third">
<label>Sta nudimo</label>
<textarea
type="text"
placeholder="ex. Full Remote position... (Separate every offer with | sign)"
value={whatWeOffer}
onChange={(e) => setWhatWeOffer(e.target.value)}
rows={5}
></textarea>
</div>
</div>

<div className="add-ad-modal-actions">
<button
className="c-btn c-btn--primary-outlined"
onClick={onDecrementStage}
>
NAZAD
</button>
<button className="c-btn c-btn--primary" onClick={completeStageHandler}>
DODAJ OGLAS
</button>
</div>
</div>
);
};

AddAdModalThirdStage.propTypes = {
onDecrementStage: PropTypes.func,
keyResponsibilities: PropTypes.any,
setKeyResponsibilities: PropTypes.func,
requirements: PropTypes.any,
setRequirements: PropTypes.func,
whatWeOffer: PropTypes.any,
setWhatWeOffer: PropTypes.func,
onAddAd: PropTypes.func,
};

export default AddAdModalThirdStage;

+ 55
- 22
src/pages/AdsPage/AdsPage.js Просмотреть файл

@@ -21,6 +21,9 @@ import { useTheme } from "@emotion/react";
import { useMediaQuery } from "@mui/material";
import { selectArchiveAds } from "../../store/selectors/archiveAdsSelectors";
import { setArchiveAdsReq } from "../../store/actions/archiveAds/archiveAdsActions";
import noActiveAds from "../../assets/images/no_active_ads.png";
import { setTechnologiesReq } from "../../store/actions/technologies/technologiesActions";
import { selectTechnologies } from "../../store/selectors/technologiesSelectors";

const AdsPage = ({ history }) => {
const theme = useTheme();
@@ -28,6 +31,7 @@ const AdsPage = ({ history }) => {
const [toggleFiltersDrawer, setToggleFiltersDrawer] = useState(false);
const [toggleModal, setToggleModal] = useState(false);
const ads = useSelector(selectAds);
const technologies = useSelector(selectTechnologies);
const archiveAds = useSelector(selectArchiveAds);
const activeAdsSliderRef = useRef();
const archiveAdsSliderRef = useRef();
@@ -35,6 +39,7 @@ const AdsPage = ({ history }) => {
const dispatch = useDispatch();

useEffect(() => {
dispatch(setTechnologiesReq());
dispatch(setAdsReq());
dispatch(setArchiveAdsReq());
}, []);
@@ -58,7 +63,7 @@ const AdsPage = ({ history }) => {
settings: {
slidesToShow: 3,
slidesToScroll: 3,
infinite: true,
infinite: false,
dots: false,
},
},
@@ -124,6 +129,7 @@ const AdsPage = ({ history }) => {
<AdFilters
open={toggleFiltersDrawer}
handleClose={handleToggleFiltersDrawer}
technologies={technologies}
/>
<AddAdModal open={toggleModal} handleClose={handleToggleModal} />
<div className="ads">
@@ -140,9 +146,11 @@ const AdsPage = ({ history }) => {
<button onClick={activeAdsArrowLeftHandler}>
<img src={arrow_left} alt="arrow-left" />
</button>
<button onClick={activeAdsArrowRightHandler}>
<img src={arrow_right} alt="arrow-right" />
</button>
{ads.length > 3 && (
<button onClick={activeAdsArrowRightHandler}>
<img src={arrow_right} alt="arrow-right" />
</button>
)}
</div>
)}
</div>
@@ -172,14 +180,31 @@ const AdsPage = ({ history }) => {
</div>
</div>
)}
{matches && (
{ads && ads.length > 0 && 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>
{ads.length > 3 && (
<button onClick={activeAdsArrowRightHandler}>
<img src={arrow_right} alt="arrow-right" />
</button>
)}
</div>
)}
{(!ads || ads.length === 0) && (
<div className="active-ads-ads-no-ads">
<img src={noActiveAds} alt="noActiveAds" />
<h1>Nažalost, trenutno nema aktivnih oglasa</h1>
<p>Uvek možete dodati novi u samo par jednostavnih koraka</p>
<div className="add-ad add-ad-no-ads">
<IconButton
className="c-btn c-btn--primary add-ad-btn"
onClick={handleToggleModal}
>
Dodaj Oglas
</IconButton>
</div>
</div>
)}
{archiveAds && archiveAds.length > 0 && (
@@ -194,9 +219,11 @@ const AdsPage = ({ history }) => {
<button onClick={archiveAdsArrowLeftHandler}>
<img src={arrow_left} alt="arrow-left" />
</button>
<button onClick={archiveAdsArrowRightHandler}>
<img src={arrow_right} alt="arrow-right" />
</button>
{archiveAds.length > 5 && (
<button onClick={archiveAdsArrowRightHandler}>
<img src={arrow_right} alt="arrow-right" />
</button>
)}
</div>
</div>
)}
@@ -230,23 +257,29 @@ const AdsPage = ({ history }) => {
<button onClick={archiveAdsArrowLeftHandler}>
<img src={arrow_left} alt="arrow-left" />
</button>
<button onClick={archiveAdsArrowRightHandler}>
<img src={arrow_right} alt="arrow-right" />
</button>
{archiveAds.length > 5 && (
<button onClick={archiveAdsArrowRightHandler}>
<img src={arrow_right} alt="arrow-right" />
</button>
)}
</div>
)}

<div className="archive-ads-no-active-ads"></div>
</div>
)}
</div>

<div className="add-ad">
<IconButton
className="c-btn c-btn--primary add-ad-btn"
onClick={handleToggleModal}
>
+ Oglas
</IconButton>
</div>
{ads && ads.length > 0 && (
<div className="add-ad">
<IconButton
className="c-btn c-btn--primary add-ad-btn"
onClick={handleToggleModal}
>
+ Oglas
</IconButton>
</div>
)}
</>
);
};

+ 14
- 2
src/request/adsRequest.js Просмотреть файл

@@ -1,7 +1,19 @@
import { getRequest } from ".";
import { getRequest, postRequest } from ".";
import apiEndpoints from "./apiEndpoints";

export const getAllAds = () => getRequest(apiEndpoints.ads.allAds);
export const getAllFilteredAds = (payload) => {
let technologiesQuery = "";
for (let i = 0; i < payload.technologies.length; i++) {
technologiesQuery += `technologies=${payload.technologies[i]}&`;
}
return getRequest(
apiEndpoints.ads.allFilteredAds +
`?minExperience=${payload.minExperience}&maxExperience=${payload.maxExperience}&workHour=${payload.workHour}&employmentType=${payload.employmentType}&${technologiesQuery}`
);
};
export const getAllArchiveAds = () =>
getRequest(apiEndpoints.ads.allArchiveAds);
export const getAdDetailsById = (id) => getRequest(`${apiEndpoints.ads.adDetails}/${id}`);
export const getAdDetailsById = (id) =>
getRequest(`${apiEndpoints.ads.adDetails}/${id}`);
export const createNewAd = (ad) => postRequest(apiEndpoints.ads.createAd, ad);

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

@@ -20,9 +20,14 @@ export default {
},
ads: {
allAds: base + "/ads",
createAd: base + "/ads",
allFilteredAds: base + "/ads/filtered",
allArchiveAds: base + "/ads/archive",
adDetails: base + "/ads/details",
},
technologies: {
allTechnologies: base + "/technologies",
},
comments:{
addComment:base + '/comments'
},

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

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

export const getAllTechnologies = () =>
getRequest(apiEndpoints.technologies.allTechnologies);

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

@@ -0,0 +1,5 @@
export const FETCH_ADD_AD_TECHNOLOGIES_REQ = "FETCH_ADD_AD_TECHNOLOGIES_REQ";
export const FETCH_ADD_AD_TECHNOLOGIES_ERR = "FETCH_ADD_AD_TECHNOLOGIES_ERR";
export const FETCH_ADD_AD_TECHNOLOGIES_SUCCESS = "FETCH_ADD_AD_TECHNOLOGIES_SUCCESS";

export const CHANGE_ISCHECKED_VALUE_ADD_AD = "CHANGE_ISCHECKED_VALUE";

+ 26
- 0
src/store/actions/addAdTechnologies/addAdTechnologiesActions.js Просмотреть файл

@@ -0,0 +1,26 @@
import {
FETCH_ADD_AD_TECHNOLOGIES_REQ,
FETCH_ADD_AD_TECHNOLOGIES_ERR,
FETCH_ADD_AD_TECHNOLOGIES_SUCCESS,
CHANGE_ISCHECKED_VALUE_ADD_AD,
} from "./addAdTechnologiesActionConstants";
export const setTechnologiesAddAdReq = () => ({
type: FETCH_ADD_AD_TECHNOLOGIES_REQ,
});
export const setTechnologiesAddAdError = (payload) => ({
type: FETCH_ADD_AD_TECHNOLOGIES_ERR,
payload,
});
export const setTechnologiesAddAd = (payload) => ({
type: FETCH_ADD_AD_TECHNOLOGIES_SUCCESS,
payload,
});
export const changeIsCheckedAddAdValue = (payload) => ({
type: CHANGE_ISCHECKED_VALUE_ADD_AD,
payload,
});

+ 18
- 0
src/store/actions/ads/adsAction.js Просмотреть файл

@@ -2,6 +2,9 @@ import {
FETCH_ADS_REQ,
FETCH_ADS_ERR,
FETCH_ADS_SUCCESS,
FETCH_FILTERED_ADS_REQ,
FETCH_FILTERED_ADS_ERR,
FETCH_FILTERED_ADS_SUCCESS,
} from "./adsActionConstants";

export const setAdsReq = () => ({
@@ -17,3 +20,18 @@ export const setAds = (payload) => ({
type: FETCH_ADS_SUCCESS,
payload,
});

export const setFilteredAdsReq = (payload) => ({
type: FETCH_FILTERED_ADS_REQ,
payload,
});

export const setFilteredAdsError = (payload) => ({
type: FETCH_FILTERED_ADS_ERR,
payload,
});

export const setFilteredAds = (payload) => ({
type: FETCH_FILTERED_ADS_SUCCESS,
payload,
});

+ 5
- 1
src/store/actions/ads/adsActionConstants.js Просмотреть файл

@@ -1,3 +1,7 @@
export const FETCH_ADS_REQ = 'FETCH_ADS_REQ';
export const FETCH_ADS_ERR = 'FETCH_ADS_ERR';
export const FETCH_ADS_SUCCESS = 'FETCH_ADS_SUCCESS';
export const FETCH_ADS_SUCCESS = 'FETCH_ADS_SUCCESS';

export const FETCH_FILTERED_ADS_REQ = 'FETCH_FILTERED_ADS_REQ';
export const FETCH_FILTERED_ADS_ERR = 'FETCH_FILTERED_ADS_ERR';
export const FETCH_FILTERED_ADS_SUCCESS = 'FETCH_FILTERED_ADS_SUCCESS';

+ 14
- 0
src/store/actions/createAd/createAdActionConstants.js Просмотреть файл

@@ -0,0 +1,14 @@
import {
createErrorType,
createFetchType,
createLoadingType,
createSuccessType,
} from "../actionHelpers";
const CREATE_AD_SCOPE = "CREATE_AD";
export const CREATE_AD_REQ = createFetchType(CREATE_AD_SCOPE);
export const CREATE_AD_ERR = createErrorType(CREATE_AD_SCOPE);
export const CREATE_AD_SUCCESS = createSuccessType(CREATE_AD_SCOPE);
export const CREATE_AD_LOADING = createLoadingType(CREATE_AD_SCOPE);

+ 21
- 0
src/store/actions/createAd/createAdActions.js Просмотреть файл

@@ -0,0 +1,21 @@
import {
CREATE_AD_ERR,
CREATE_AD_REQ,
CREATE_AD_SUCCESS,
} from "./createAdActionConstants";
export const setCreateAdReq = (payload) => ({
type: CREATE_AD_REQ,
payload,
});
export const setCreateAdError = (payload) => ({
type: CREATE_AD_ERR,
payload,
});
export const setCreateAd = (payload) => ({
type: CREATE_AD_SUCCESS,
payload,
});

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

@@ -0,0 +1,5 @@
export const FETCH_TECHNOLOGIES_REQ = "FETCH_TECHNOLOGIES_REQ";
export const FETCH_TECHNOLOGIES_ERR = "FETCH_TECHNOLOGIES_ERR";
export const FETCH_TECHNOLOGIES_SUCCESS = "FETCH_TECHNOLOGIES_SUCCESS";

export const CHANGE_ISCHECKED_VALUE = "CHANGE_ISCHECKED_VALUE";

+ 25
- 0
src/store/actions/technologies/technologiesActions.js Просмотреть файл

@@ -0,0 +1,25 @@
import {
FETCH_TECHNOLOGIES_ERR,
FETCH_TECHNOLOGIES_SUCCESS,
FETCH_TECHNOLOGIES_REQ,
CHANGE_ISCHECKED_VALUE
} from "./technologiesActionConstants";
export const setTechnologiesReq = () => ({
type: FETCH_TECHNOLOGIES_REQ,
});
export const setTechnologiesError = (payload) => ({
type: FETCH_TECHNOLOGIES_ERR,
payload,
});
export const setTechnologies = (payload) => ({
type: FETCH_TECHNOLOGIES_SUCCESS,
payload,
});
export const changeIsCheckedValue = (payload) => ({
type: CHANGE_ISCHECKED_VALUE,
payload,
});

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

@@ -1,6 +1,8 @@
import {
FETCH_ADS_ERR,
FETCH_ADS_SUCCESS,
FETCH_FILTERED_ADS_ERR,
FETCH_FILTERED_ADS_SUCCESS,
} from "../../actions/ads/adsActionConstants";
import createReducer from "../../utils/createReducer";

@@ -13,6 +15,8 @@ export default createReducer(
{
[FETCH_ADS_SUCCESS]: setStateAds,
[FETCH_ADS_ERR]: setAdsErrorMessage,
[FETCH_FILTERED_ADS_SUCCESS]: setStateFilteredAds,
[FETCH_FILTERED_ADS_ERR]: setFilteredAdsErrorMessage,
},
initialState
);
@@ -30,3 +34,17 @@ function setAdsErrorMessage(state, action) {
errorMessage: action.payload,
};
}

function setStateFilteredAds(state, action) {
return {
...state,
ads: action.payload,
};
}

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

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

@@ -0,0 +1,26 @@
import {
CREATE_AD_SUCCESS,
CREATE_AD_ERR,
} from "../../actions/createAd/createAdActionConstants";
import createReducer from "../../utils/createReducer";

const initialState = {
ad: null,
errorMessage: "",
};

export default createReducer(
{
[CREATE_AD_SUCCESS]: setStateCreateAd,
[CREATE_AD_ERR]: setStateErrorMessage,
},
initialState
);

function setStateCreateAd(state, action) {
return { ...state, ad: action.payload };
}

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

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

@@ -1,19 +1,22 @@
import { combineReducers } from 'redux';
import loginReducer from './login/loginReducer';
import loadingReducer from './loading/loadingReducer';
import userReducer from './user/userReducer';
import randomDataReducer from './randomData/randomDataReducer';
import usersReducer from './user/usersReducer';
import candidateReducer from './candidate/candidateReducer';
import { combineReducers } from "redux";
import loginReducer from "./login/loginReducer";
import loadingReducer from "./loading/loadingReducer";
import userReducer from "./user/userReducer";
import randomDataReducer from "./randomData/randomDataReducer";
import usersReducer from "./user/usersReducer";
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 technologiesReducer from "./technology/technologiesReducer";
import addAddTechnologiesReducer from "./technology/addAddTechnologiesReducer";
import createAdReducer from "./ad/createAdReducer";
import processesReducer from "./processes/processesReducer";
import processReducer from "./processes/processReducer";
import applicantWithProcessesReducer from "./processes/applicantWithProcessesReducer";
import userDetailsReducer from './user/userDetailsReducer';
import inviteUserReducer from './user/inviteUserReducer';
import userDetailsReducer from "./user/userDetailsReducer";
import inviteUserReducer from "./user/inviteUserReducer";

export default combineReducers({
login: loginReducer,
@@ -21,16 +24,17 @@ export default combineReducers({
loading: loadingReducer,
randomData: randomDataReducer,
users: usersReducer,
candidate:candidateReducer,
candidate: candidateReducer,
ads: adsReducer,
ad: adReducer,
archiveAds: archiveAdsReducer,
technologies: technologiesReducer,
addAdTechnologies: addAddTechnologiesReducer,
createAd: createAdReducer,
candidates: candidatesReducer,
processes: processesReducer,
process: processReducer,
applicant: applicantWithProcessesReducer,
userDetails: userDetailsReducer,
invite: inviteUserReducer

invite: inviteUserReducer,
});

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

@@ -0,0 +1,39 @@
import createReducer from "../../utils/createReducer";
import {
FETCH_ADD_AD_TECHNOLOGIES_ERR,
FETCH_ADD_AD_TECHNOLOGIES_SUCCESS,
CHANGE_ISCHECKED_VALUE_ADD_AD,
} from "../../actions/addAdTechnologies/addAdTechnologiesActionConstants";

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

export default createReducer(
{
[FETCH_ADD_AD_TECHNOLOGIES_SUCCESS]: setStateTechnologiesAddAd,
[FETCH_ADD_AD_TECHNOLOGIES_ERR]: setStateAddAdErrorMessage,
[CHANGE_ISCHECKED_VALUE_ADD_AD]: setIsCheckedTechnologyAddAd,
},
initialState
);

function setStateTechnologiesAddAd(state, action) {
return { ...state, technologies: action.payload };
}

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

function setIsCheckedTechnologyAddAd(state, action) {
return {
...state,
technologies: state.technologies.map((tech) =>
tech.technologyId === action.payload
? { ...tech, isChecked: !tech.isChecked }
: tech
),
};
}

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

@@ -0,0 +1,45 @@
import createReducer from "../../utils/createReducer";
import {
FETCH_TECHNOLOGIES_SUCCESS,
FETCH_TECHNOLOGIES_ERR,
CHANGE_ISCHECKED_VALUE,
} from "../../actions/technologies/technologiesActionConstants";

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

export default createReducer(
{
[FETCH_TECHNOLOGIES_SUCCESS]: setStateTechnologies,
[FETCH_TECHNOLOGIES_ERR]: setStateErrorMessage,
[CHANGE_ISCHECKED_VALUE]: setIsCheckedTechnology,
},
initialState
);

function setStateTechnologies(state, action) {
return { ...state, technologies: action.payload };
}

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

function setIsCheckedTechnology(state, action) {
const tmpIndex = state.technologies.findIndex(
(x) => x.name === action.payload
);

if (tmpIndex === -1) {
return state;
}

return {
...state,
technologies: state.technologies.map((tech, index) =>
tmpIndex === index ? { ...tech, isChecked: !tech.isChecked } : tech
),
};
}

+ 35
- 1
src/store/saga/adsSaga.js Просмотреть файл

@@ -3,16 +3,29 @@ import {
getAllAds,
getAdDetailsById,
getAllArchiveAds,
getAllFilteredAds,
createNewAd,
} from "../../request/adsRequest";
import { setAds, setAdsError } from "../actions/ads/adsAction";
import {
setAds,
setAdsError,
setFilteredAds,
setFilteredAdsError,
} from "../actions/ads/adsAction";
import { setAd, setAdError } from "../actions/ad/adActions";
import {
setArchiveAds,
setArchiveAdsError,
} from "../actions/archiveAds/archiveAdsActions";
import { FETCH_ADS_REQ } from "../actions/ads/adsActionConstants";
import { FETCH_FILTERED_ADS_REQ } from "../actions/ads/adsActionConstants";
import { FETCH_AD_REQ } from "../actions/ad/adActionConstants";
import { FETCH_ARCHIVE_ADS_REQ } from "../actions/archiveAds/archiveAdsActionConstants";
import { CREATE_AD_REQ } from "../actions/createAd/createAdActionConstants";
import {
setCreateAd,
setCreateAdError,
} from "../actions/createAd/createAdActions";

export function* getAds() {
try {
@@ -23,6 +36,15 @@ export function* getAds() {
}
}

export function* getFilteredAds({ payload }) {
try {
const result = yield call(getAllFilteredAds, payload);
yield put(setFilteredAds(result.data));
} catch (error) {
yield put(setFilteredAdsError(error));
}
}

export function* getAd({ payload }) {
try {
const result = yield call(getAdDetailsById, payload.id);
@@ -41,10 +63,22 @@ export function* getArchiveAds() {
}
}

export function* createAd({ payload }) {
try {
const result = yield call(createNewAd, payload);
const ad = result.data;
yield put(setCreateAd(ad));
} catch (error) {
yield put(setCreateAdError(error));
}
}

export default function* adsSaga() {
yield all([
takeLatest(FETCH_ADS_REQ, getAds),
takeLatest(FETCH_FILTERED_ADS_REQ, getFilteredAds),
takeLatest(FETCH_AD_REQ, getAd),
takeLatest(FETCH_ARCHIVE_ADS_REQ, getArchiveAds),
takeLatest(CREATE_AD_REQ, createAd),
]);
}

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

@@ -2,6 +2,7 @@ import { all } from "redux-saga/effects";
import adsSaga from "./adsSaga";
import candidatesSaga from './candidatesSaga';
import loginSaga from "./loginSaga";
import technologiesSaga from "./technologiesSaga";
import usersSaga from "./usersSaga";
import processesSaga from "./processSaga";

@@ -10,6 +11,7 @@ export default function* rootSaga() {
loginSaga(),
usersSaga(),
adsSaga(),
technologiesSaga(),
candidatesSaga(),
processesSaga(),
]);

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

@@ -0,0 +1,36 @@
import { all, call, put, takeLatest } from "redux-saga/effects";
import { getAllTechnologies } from "../../request/technologiesRequest";
import { FETCH_ADD_AD_TECHNOLOGIES_REQ } from "../actions/addAdTechnologies/addAdTechnologiesActionConstants";
import { setTechnologiesAddAd } from "../actions/addAdTechnologies/addAdTechnologiesActions";
import { FETCH_TECHNOLOGIES_REQ } from "../actions/technologies/technologiesActionConstants";
import {
setTechnologies,
setTechnologiesError,
} from "../actions/technologies/technologiesActions";

export function* getTechnologies() {
try {
const result = yield call(getAllTechnologies);
const resultData = result.data.map((res) => ({ ...res, isChecked: false }));
yield put(setTechnologies(resultData));
} catch (error) {
yield put(setTechnologiesError(error));
}
}

export function* getTechnologiesAddAd() {
try {
const result = yield call(getAllTechnologies);
const resultData = result.data.map((res) => ({ ...res, isChecked: false }));
yield put(setTechnologiesAddAd(resultData));
} catch (error) {
yield put(setTechnologiesError(error));
}
}

export default function* technologiesSaga() {
yield all([
takeLatest(FETCH_TECHNOLOGIES_REQ, getTechnologies),
takeLatest(FETCH_ADD_AD_TECHNOLOGIES_REQ, getTechnologiesAddAd),
]);
}

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

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

export const technologiesSelector = (state) => state.technologies;

export const selectTechnologies = createSelector(
technologiesSelector,
(state) => state.technologies
);

export const selectTechnologiesError = createSelector(
technologiesSelector,
(state) => state.errorMessage
);

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