瀏覽代碼

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

feature/selection_process_FE
safet.purkovic 3 年之前
父節點
當前提交
7266d9952a

+ 3
- 0
src/AppRoutes.js 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

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

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

+ 4
- 1
src/request/apiEndpoints.js 查看文件

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

+ 19
- 0
src/store/actions/ads/adsAction.js 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

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

Loading…
取消
儲存