Przeglądaj źródła

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

feature/selection_process_FE
safet.purkovic 3 lat temu
rodzic
commit
7266d9952a

+ 3
- 0
src/AppRoutes.js Wyświetl plik

@@ -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 Wyświetl plik

@@ -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 Wyświetl plik

@@ -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 Wyświetl plik

@@ -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 Wyświetl plik

@@ -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 Wyświetl plik

@@ -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 Wyświetl plik

@@ -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 Wyświetl plik

@@ -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 Wyświetl plik

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

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

+ 4
- 1
src/request/apiEndpoints.js Wyświetl plik

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

+ 19
- 0
src/store/actions/ads/adsAction.js Wyświetl plik

@@ -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 Wyświetl plik

@@ -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 Wyświetl plik

@@ -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 Wyświetl plik

@@ -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 Wyświetl plik

@@ -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 Wyświetl plik

@@ -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 Wyświetl plik

@@ -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 Wyświetl plik

@@ -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 Wyświetl plik

@@ -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 Wyświetl plik

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

Ładowanie…
Anuluj
Zapisz