Quellcode durchsuchen

Merge branch 'feature/1518_ad_details_fe' of Neca/HRCenter into FE_dev

feature/selection_process_FE
safet.purkovic vor 3 Jahren
Ursprung
Commit
7266d9952a

+ 3
- 0
src/AppRoutes.js Datei anzeigen

@@ -4,6 +4,7 @@ import { Redirect, Route, Switch } from "react-router-dom";
import {
HOME_PAGE,
ADS_PAGE,
AD_DETAILS_PAGE,
FORGOT_PASSWORD_PAGE,
FORGOT_PASSWORD_CONFIRMATION_PAGE,
NOT_FOUND_PAGE,
@@ -28,6 +29,7 @@ import ForgotPasswordConfirmationPage from "./pages/ForgotPasswordPage/ForgotPas
import ResetPasswordPage from "./pages/ForgotPasswordPage/ResetPasswordPageMUI";
import UsersPage from "./pages/UsersPage/UsersPage";
import CandidatesPage from './pages/CandidatesPage/CandidatesPage'
import AdDetailsPage from "./pages/AdsPage/AdDetailsPage";

const AppRoutes = () => (
<Switch>
@@ -43,6 +45,7 @@ const AppRoutes = () => (
<Route path={RESET_PASSWORD_PAGE} component={ResetPasswordPage} />
<PrivateRoute exact path={HOME_PAGE} component={HomePage} />
<PrivateRoute exact path={ADS_PAGE} component={AdsPage} />
<PrivateRoute exact path={AD_DETAILS_PAGE} component={AdDetailsPage} />
<PrivateRoute exact path={USERS_PAGE} component={UsersPage} />
<PrivateRoute exact path={CANDIDATES_PAGE} component={CandidatesPage} />
<Redirect from="*" to={NOT_FOUND_PAGE} />

+ 98
- 0
src/assets/styles/components/_ads.scss Datei anzeigen

@@ -174,6 +174,7 @@ h3 {
border-radius: 18px;
gap: 18px;
margin-right: 36px;
cursor: pointer;
}

.ad-card-date p {
@@ -339,4 +340,101 @@ h3 {

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

.ad-details {
padding: 45px 72px 18px 223px;
}

.ad-details-tech-logo {
position: relative;
left: -80px;
display: flex;
align-items: center;
justify-content: space-between;
}

.ad-details-tech-logo-title {
display: flex;
align-items: center;
}

.ad-details-tech-logo-title-img {
margin-right: 18px;
}

.ad-details-tech-logo-title-sub sub {
margin-left: 9px;
font-size: 1.25rem;
color: $mainBlue !important;
font-weight: 600;
}

.ad-details-tech-logo-date p span {
color: #9d9d9d;
}

.ad-details-content-experience {
margin-top: 18px;
}

.ad-details-content-experience p {
color: #272727;
font-family: "Source Sans Pro";
font-style: normal;
font-weight: 400;
font-size: 16px;
line-height: 20px;
}

.ad-details-content-work-time {
margin-top: 18px;
}

.ad-details-content-work-time button {
box-sizing: border-box;
flex-direction: row;
padding: 9px;
gap: 10px;
width: 76px;
height: 38px;
border: 1px solid #e4e4e4;
border-radius: 9px;
flex: none;
order: 0;
flex-grow: 0;
margin-right: 18px;
cursor: pointer;
}

.ad-details-content-content {
margin-top: 18px;
}

.ad-details-content-conten-description {
margin-top: 18px;
}

.ad-details-content-conten-description h3 {
margin-bottom: 9px;
}

.ad-details-content-conten-description ul {
list-style: circle;
padding-left: 36px;
}

.ad-details-buttons {
display: flex;
justify-content: flex-end;
align-items: center;
margin-top: 36px;
}

.ad-details-buttons > button {
margin-left: 36px;
}

.ad-details-buttons-link {
color: $mainBlue;
}

+ 24
- 5
src/components/Ads/Ad.js Datei anzeigen

@@ -1,15 +1,25 @@
import React from "react";
import PropTypes from "prop-types";
import logoReact from "../../assets/images/logo_react.png";

const Ad = () => {
const Ad = ({
title,
minimumExperience,
createdAt,
expiredAt,
onShowAdDetails,
}) => {
return (
<div className="ad-card">
<div className="ad-card" onClick={onShowAdDetails}>
<div className="ad-card-date">
<p>30.09.22 - 30.10.22</p>
<p>
{new Date(createdAt).toLocaleDateString()} -{" "}
{new Date(expiredAt).toLocaleDateString()}
</p>
</div>

<div className="ad-card-title">
<h3>React Developer</h3>
<h3>{title}</h3>
</div>

<div className="ad-card-logo">
@@ -17,7 +27,7 @@ const Ad = () => {
</div>

<div className="ad-card-experience">
<p>3+ years of experience</p>
<p>{minimumExperience}+ years of experience</p>
</div>

<div className="ad-card-buttons">
@@ -29,4 +39,13 @@ const Ad = () => {
);
};

Ad.propTypes = {
id: PropTypes.number,
title: PropTypes.string,
minimumExperience: PropTypes.number,
createdAt: PropTypes.any,
expiredAt: PropTypes.any,
onShowAdDetails: PropTypes.func,
};

export default Ad;

+ 29
- 0
src/components/Button/FilterButton.js Datei anzeigen

@@ -0,0 +1,29 @@
import { IconButton } from "@mui/material";
import PropTypes from "prop-types";
import React from "react";
import filters from "../../assets/images/filters.png";

const FilterButton = ({ onShowFilters }) => {
return (
<IconButton
className={"c-btn--primary-outlined c-btn userPageBtn ml-20px no-padding"}
onClick={onShowFilters}
>
Filteri
<img
style={{
position: "relative",
top: -0.25,
marginLeft: "9px",
}}
src={filters}
/>
</IconButton>
);
};

FilterButton.propTypes = {
onShowFilters: PropTypes.func,
};

export default FilterButton;

+ 5
- 5
src/components/MUI/NavbarComponent.js Datei anzeigen

@@ -215,11 +215,11 @@ const NavbarComponent = () => {
display: "flex",
justifyContent: "center",
alignItems: "center",
width: matches ? '100%' : 'auto'
width: matches ? "100%" : "auto",
}}
>
{matches ? (
<Box
<Box
className="responsive-nav-cont"
style={{
display: "flex",
@@ -230,14 +230,14 @@ const NavbarComponent = () => {
<img
style={{ height: "37px", width: "37px", marginLeft: "0px" }}
src={HrLogo}
className='responsive-logo'
className="responsive-logo"
/>
<div
style={{
display: "flex",
alignItems: "center",
}}
className='icons-cont'
className="icons-cont"
>
<img src={searchIcon} />
<IconButton
@@ -297,7 +297,7 @@ const NavbarComponent = () => {
}}
className="text-black"
as={Link}
to={n}
to={`/${n}`}
>
{t("nav." + n)}
</Typography>

+ 1
- 0
src/constants/pages.js Datei anzeigen

@@ -2,6 +2,7 @@ export const BASE_PAGE = '/';
export const FORGOT_PASSWORD_PAGE = '/forgot-password';
export const HOME_PAGE = '/home';
export const ADS_PAGE = '/ads';
export const AD_DETAILS_PAGE = '/ads/:id';
export const ERROR_PAGE = '/error-page';
export const NOT_FOUND_PAGE = '/not-found';
export const USERS_PAGE = '/users';

+ 107
- 0
src/pages/AdsPage/AdDetailsPage.js Datei anzeigen

@@ -0,0 +1,107 @@
import { IconButton } from "@mui/material";
import React from "react";
import aspNetIcon from "../../assets/images/.net_icon.png";
import { Link } from "react-router-dom";

const AdDetailsPage = () => {
return (
<div className="ad-details">
<div className="ad-details-tech-logo">
<div className="ad-details-tech-logo-title">
<div className="ad-details-tech-logo-title-img">
<img src={aspNetIcon} alt="asp-net-icon" />
</div>
<div className="ad-details-tech-logo-title-title">
<h1>.NET Developer</h1>
</div>
<div className="ad-details-tech-logo-title-sub">
<sub>| 4 prijavljenih</sub>
</div>
</div>
<div className="ad-details-tech-logo-date">
<p>
<span>Rok prijave do: </span>14.12.2022.
</p>
</div>
</div>
<div className="ad-details-content">
<div className="ad-details-content-experience">
<p>3+ years of experience</p>
</div>
<div className="ad-details-content-work-time">
<button>Posao</button>
<button>Full-time</button>
</div>
<div className="ad-details-content-content">
<div className="ad-details-content-conten-description">
<p>
Team Diligent is constantly growing! We are looking for a team
player that will work with experienced engineers. If technology is
your passion and you are ready to move the boundaries of your
knowledge every day, then, Diligent is the right place for you. If
you are not from Niš, we are offering a full remote position.
</p>
</div>
<div className="ad-details-content-conten-description">
<h3>Key Responsibilities</h3>
<ul>
<li>
Working as a full-stack developer on various project and
products
</li>
<li>Working with 3rd-party APIs</li>
<li>Working on different integration scenarios</li>
<li>Setting up project structure and architecture</li>
<li>
Being involved in full project development, from writing a
specification to deploying a finished product
</li>
</ul>
</div>
<div className="ad-details-content-conten-description">
<h3>Requirements</h3>
<ul>
<li>
Good software development fundamentals and knowledge of .NET
architecture concepts & patterns
</li>
<li>Good knowledge of software design patterns</li>
<li>Good knowledge of databases and database design</li>
<li>Experience in working with microservices is a big plus</li>
<li>
The ability to work in a big team but also to work independently
</li>
<li>Excellent communication skills</li>
</ul>
</div>
<div className="ad-details-content-conten-description">
<h3>What we offer</h3>
<ul>
<li>Full Remote position</li>
<li>
A fast-growth company with stable projects and strong
international clients
</li>
<li>Opportunity to work in teams with experienced engineers</li>
<li>Competitive employment conditions</li>
<li>
An environment that will make you feel good about your job
</li>
<li>Challenging and diverse projects</li>
<li>Support in your personal and professional growth</li>
<li>Flexible working hours Private health insurance</li>
</ul>
</div>
</div>
<div className="ad-details-buttons">
<Link className="ad-details-buttons-link" to='/ads'>Nazad na sve oglase</Link>
<IconButton className="c-btn c-btn--primary add-ad-btn">
PRIJAVI SE
</IconButton>
</div>
</div>
</div>
);
};

export default AdDetailsPage;

+ 54
- 30
src/pages/AdsPage/AdsPage.js Datei anzeigen

@@ -1,18 +1,33 @@
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import PropTypes from "prop-types";
import Ad from "../../components/Ads/Ad";
import ArchiveAd from "../../components/Ads/ArchiveAd";
import IconButton from "../../components/IconButton/IconButton";
import filterVector from "../../assets/images/filter_vector.png";
import arrow_left from "../../assets/images/arrow_left.png";
import arrow_right from "../../assets/images/arrow_right.png";
import { useTranslation } from "react-i18next";
import AddAdModal from "../../components/Ads/AddAdModal";
import AdFilters from "../../components/Ads/AdFilters";
import { useDispatch } from "react-redux";
import { setAdsReq } from "../../store/actions/ads/adsAction";
import { useSelector } from "react-redux";
import { selectAds, selectAdsError } from "../../store/selectors/adsSelectors";
import { AD_DETAILS_PAGE } from "../../constants/pages";
import FilterButton from "../../components/Button/FilterButton";

const AdsPage = () => {
const AdsPage = ({ history }) => {
const [toggleFiltersDrawer, setToggleFiltersDrawer] = useState(false);
const [toggleModal, setToggleModal] = useState(false);
const ads = useSelector(selectAds);
const errorMessage = useSelector(selectAdsError);
const { t } = useTranslation();
const dispatch = useDispatch();

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

console.log(errorMessage);

const handleToggleFiltersDrawer = () => {
setToggleFiltersDrawer((oldState) => !oldState);
@@ -26,42 +41,47 @@ const AdsPage = () => {
<>
<div className="l-t-rectangle"></div>
<div className="r-b-rectangle"></div>
<AdFilters />
{/* <AdFilters /> */}
<AdFilters
open={toggleFiltersDrawer}
handleClose={handleToggleFiltersDrawer}
/>
<AddAdModal open={toggleModal} handleClose={handleToggleModal} />
<div className="ads">
<div className="active-ads">
<div className="active-ads-header">
<h1>{t("ads.activeAds")}</h1>
<IconButton
sx={{ marginLeft: "15px" }}
className="c-btn c-btn--primary-outlined"
onClick={handleToggleFiltersDrawer}
>
Filteri{" "}
<img src={filterVector} alt="filter" className="filter-vector" />
</IconButton>
</div>
<div className="active-ads-ads">
<div className="active-ads-ads-a">
<div className="active-ads-ads-arrows">
<button>
<img src={arrow_left} alt="arrow-left" />
</button>
<button>
<img src={arrow_right} alt="arrow-right" />
</button>
</div>
{ads && ads.length > 0 && (
<div className="active-ads">
<div className="active-ads-header">
<h1>{t("ads.activeAds")}</h1>
<FilterButton onShowFilters={handleToggleFiltersDrawer} />
</div>
<div className="active-ads-ads-ad">
<Ad />
<Ad />
<div className="active-ads-ads">
<div className="active-ads-ads-a">
<div className="active-ads-ads-arrows">
<button>
<img src={arrow_left} alt="arrow-left" />
</button>
<button>
<img src={arrow_right} alt="arrow-right" />
</button>
</div>
</div>
<div className="active-ads-ads-ad">
{ads.map((ad, index) => (
<Ad
onShowAdDetails={() =>
history.push(AD_DETAILS_PAGE.replace(":id", ad.id))
}
key={index}
title={ad.title}
minimumExperience={ad.minimumExperience}
createdAt={ad.createdAt}
expiredAt={ad.expiredAt}
/>
))}
</div>
</div>
</div>
</div>
)}

<div className="archived-ads">
<div className="archived-ads-header">
@@ -98,4 +118,8 @@ const AdsPage = () => {
);
};

AdsPage.propTypes = {
history: PropTypes.any,
};

export default AdsPage;

+ 4
- 0
src/request/adsRequest.js Datei anzeigen

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

export const getAllAds = () => getRequest(apiEndpoints.ads.allAds);

+ 4
- 1
src/request/apiEndpoints.js Datei anzeigen

@@ -14,5 +14,8 @@ export default {
},
candidates:{
allCandidates:base + "/applicants"
}
},
ads: {
allAds: base + "/ads",
},
};

+ 19
- 0
src/store/actions/ads/adsAction.js Datei anzeigen

@@ -0,0 +1,19 @@
import {
FETCH_ADS_REQ,
FETCH_ADS_ERR,
FETCH_ADS_SUCCESS,
} from "./adsActionConstants";

export const setAdsReq = () => ({
type: FETCH_ADS_REQ,
});

export const setAdsError = (payload) => ({
type: FETCH_ADS_ERR,
payload,
});

export const setAds = (payload) => ({
type: FETCH_ADS_SUCCESS,
payload,
});

+ 3
- 0
src/store/actions/ads/adsActionConstants.js Datei anzeigen

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

+ 32
- 0
src/store/reducers/ad/adsReducer.js Datei anzeigen

@@ -0,0 +1,32 @@
import {
FETCH_ADS_ERR,
FETCH_ADS_SUCCESS,
} from "../../actions/ads/adsActionConstants";
import createReducer from "../../utils/createReducer";

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

export default createReducer(
{
[FETCH_ADS_SUCCESS]: setStateAds,
[FETCH_ADS_ERR]: setAdsErrorMessage,
},
initialState
);

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

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

+ 9
- 7
src/store/reducers/index.js Datei anzeigen

@@ -1,16 +1,18 @@
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 { 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 adsReducer from "./ad/adsReducer";
import candidatesReducer from './candidates/candidatesReducer';

export default combineReducers({
login: loginReducer,
user: userReducer,
loading:loadingReducer,
loading: loadingReducer,
randomData: randomDataReducer,
users: usersReducer,
ads: adsReducer,
candidates:candidatesReducer
});

+ 0
- 1
src/store/reducers/user/usersReducer.js Datei anzeigen

@@ -19,7 +19,6 @@ export default createReducer(
);

function setStateUsers(state, action) {
console.log("POZIV");
return {
...state,
users: action.payload,

+ 17
- 0
src/store/saga/adsSaga.js Datei anzeigen

@@ -0,0 +1,17 @@
import { all, call, put, takeLatest } from "redux-saga/effects";
import { getAllAds } from "../../request/adsRequest";
import { setAds, setAdsError } from "../actions/ads/adsAction";
import { FETCH_ADS_REQ } from "../actions/ads/adsActionConstants";

export function* getAds() {
try {
const result = yield call(getAllAds);
yield put(setAds(result.data));
} catch (error) {
yield put(setAdsError(error));
}
}

export default function* adsSaga() {
yield all([takeLatest(FETCH_ADS_REQ, getAds)]);
}

+ 5
- 3
src/store/saga/index.js Datei anzeigen

@@ -1,12 +1,14 @@
import { all } from 'redux-saga/effects';
import { all } from "redux-saga/effects";
import adsSaga from "./adsSaga";
import candidatesSaga from './candidatesSaga';
import loginSaga from './loginSaga';
import usersSaga from './usersSaga';
import loginSaga from "./loginSaga";
import usersSaga from "./usersSaga";

export default function* rootSaga() {
yield all([
loginSaga(),
usersSaga(),
adsSaga(),
candidatesSaga()
]);
}

+ 2
- 2
src/store/saga/loginSaga.js Datei anzeigen

@@ -94,9 +94,9 @@ function* forgetPassword({ payload }) {

function* resetPassword({ payload }) {
try {
console.log(payload)
// console.log(payload)
const { data } = yield call(sendResetPassword, payload);
console.log(data);
// console.log(data);
yield put(forgetPasswordSuccess(data));
if (payload.handleApiResponseSuccess) {
yield call(payload.handleApiResponseSuccess);

+ 1
- 1
src/store/saga/usersSaga.js Datei anzeigen

@@ -11,7 +11,7 @@ export function* getUsers() {
const JwtToken = yield call(authScopeStringGetHelper, JWT_TOKEN);
yield call(addHeaderToken, JwtToken);
const result = yield call(getAllUsers);
console.log(result.data)
// console.log(result.data)
yield put(setUsers(result.data));
} catch (error) {
yield put(setUsersError(error))

+ 10
- 0
src/store/selectors/adsSelectors.js Datei anzeigen

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

export const adsSelector = (state) => state.ads;

export const selectAds = createSelector(adsSelector, (state) => state.ads);

export const selectAdsError = createSelector(
adsSelector,
(state) => state.errorMessage
);

Laden…
Abbrechen
Speichern