소스 검색

two language support

pull/184/head
Dzenis Hadzifejzovic 3 년 전
부모
커밋
f9ba677eee
60개의 변경된 파일1026개의 추가작업 그리고 1292개의 파일을 삭제
  1. 0
    126
      src/__tests__/UITests/addAdModalFirstStageUI.test.js
  2. 0
    73
      src/__tests__/UITests/addAdModalSecondStageUI.test.js
  3. 5
    1
      src/components/Ads/Ad.js
  4. 16
    4
      src/components/Ads/AdDetailsCandidateCard.js
  5. 18
    12
      src/components/Ads/AdFilters.js
  6. 0
    163
      src/components/Ads/AddAdModal.js
  7. 0
    151
      src/components/Ads/AddAdModalFirstStage.js
  8. 0
    143
      src/components/Ads/AddAdModalSecondStage.js
  9. 0
    77
      src/components/Ads/AddAdModalThirdStage.js
  10. 5
    2
      src/components/Ads/ApplyForAd.js
  11. 17
    11
      src/components/Ads/ApplyForAdFirstStage.js
  12. 8
    6
      src/components/Ads/ApplyForAdFourthStage.js
  13. 8
    6
      src/components/Ads/ApplyForAdSecondStage.js
  14. 22
    7
      src/components/Ads/ApplyForAdThirdStage.js
  15. 6
    2
      src/components/Ads/ArchiveAd.js
  16. 5
    3
      src/components/Ads/StatsAd.js
  17. 3
    1
      src/components/Button/EditButton.js
  18. 6
    2
      src/components/Button/FilterButton.js
  19. 4
    2
      src/components/Candidates/CandidateCard.js
  20. 12
    11
      src/components/Candidates/CandidateFilters.js
  21. 4
    2
      src/components/MUI/CommentProcessDialog.js
  22. 15
    9
      src/components/MUI/ConfirmDialog.js
  23. 6
    4
      src/components/MUI/InterviewDialog.js
  24. 7
    8
      src/components/MUI/InterviewerDialog.js
  25. 4
    4
      src/components/MUI/InviteDialog.js
  26. 2
    2
      src/components/MUI/NavbarComponent.js
  27. 8
    6
      src/components/MUI/StatusDialog.js
  28. 8
    6
      src/components/Patterns/PatternFilters.js
  29. 1
    1
      src/components/Profile/UserProfile.js
  30. 9
    15
      src/components/Registration/FirstStepForm.js
  31. 2
    2
      src/components/Registration/SecondStepForm.js
  32. 18
    15
      src/components/Schedules/DayComponent.js
  33. 4
    4
      src/components/Schedules/DayDetailsComponent.js
  34. 72
    58
      src/components/Selection/ApplicantSelection.js
  35. 16
    7
      src/components/Selection/Selection.js
  36. 5
    3
      src/components/Selection/SelectionCard.js
  37. 5
    5
      src/components/Selection/SelectionFilter.js
  38. 3
    1
      src/components/UI/CustomDrawer.js
  39. 9
    9
      src/i18n/index.js
  40. 151
    7
      src/i18n/resources/en.js
  41. 195
    47
      src/i18n/resources/rs.js
  42. 9
    7
      src/pages/AdsPage/AdDetailsPage.js
  43. 5
    5
      src/pages/AdsPage/AdsPage.js
  44. 10
    9
      src/pages/AdsPage/CreateAdFirstStep.js
  45. 7
    5
      src/pages/AdsPage/CreateAdPage.js
  46. 5
    3
      src/pages/AdsPage/CreateAdSecondStep.js
  47. 5
    3
      src/pages/AdsPage/CreateAdThirdStep.js
  48. 3
    1
      src/pages/CandidatesPage/AdsCandidatesPage.js
  49. 40
    20
      src/pages/CandidatesPage/CandidateDetailsPage.js
  50. 8
    6
      src/pages/CandidatesPage/CandidatesPage.js
  51. 9
    4
      src/pages/CandidatesPage/TableViewPage.js
  52. 8
    6
      src/pages/PatternsPage/PatternDetailsPage.js
  53. 16
    14
      src/pages/PatternsPage/PatternsPage.js
  54. 2
    2
      src/pages/RegisterPage/RegisterPage.js
  55. 3
    1
      src/pages/SchedulePage/SchedulePage.js
  56. 4
    4
      src/pages/SelectionProcessPage/SelectionProcessOfApplicantPage.js
  57. 8
    15
      src/pages/SelectionProcessPage/SelectionProcessPage.js
  58. 32
    15
      src/pages/StatsPage/StatsPage.js
  59. 168
    149
      src/pages/UsersPage/UserDetails.js
  60. 5
    5
      src/pages/UsersPage/UsersPage.js

+ 0
- 126
src/__tests__/UITests/addAdModalFirstStageUI.test.js 파일 보기

@@ -1,126 +0,0 @@
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import * as redux from "react-redux";
import store from "../../store";
import { Router } from "react-router-dom";
import history from "../../store/utils/history";
import { mockState } from "../../mockState";
import ColorModeProvider from "../../context/ColorModeContext";
import AddAdModalFirstStage from "../../components/Ads/AddAdModalFirstStage";

describe("Add ad modals ui tests", () => {
const props = {
onIncrementStage: jest.fn(),
onDecrementStage: jest.fn(),
onCompleteFirstStage: jest.fn(),
title: "Title",
employmentType: "Work",
workHour: "FullTime",
setTitle: jest.fn(),
setEmploymentType: jest.fn(),
setWorkHour: jest.fn(),
expiredAt: new Date(),
setExpiredAt: jest.fn(),
};

const cont = (
<redux.Provider store={store}>
<Router history={history}>
<ColorModeProvider>
<AddAdModalFirstStage {...props} />
</ColorModeProvider>
</Router>
</redux.Provider>
);

let spyOnUseSelector;

beforeEach(() => {
spyOnUseSelector = jest.spyOn(redux, "useSelector");
spyOnUseSelector.mockReturnValue(mockState.ads.ads);
});

afterEach(() => {
jest.restoreAllMocks();
});

it("Should render add ad modal stage", () => {
const { container } = render(cont);
const modal = container.getElementsByClassName("add-ad-modal-stage");
expect(modal).toBeDefined();
});

it("Should render work button", () => {
render(cont);
expect(screen.getByTestId("add-ad-modal-work-btn")).toBeDefined();
});

it("Should render intership button", () => {
render(cont);
expect(screen.getByTestId("add-ad-modal-intership-btn")).toBeDefined();
});

it("Should render parttime button", () => {
render(cont);
expect(screen.getByTestId("add-ad-modal-parttime-btn")).toBeDefined();
});

it("Should render fulltime button", () => {
render(cont);
expect(screen.getByTestId("add-ad-modal-fulltime-btn")).toBeDefined();
});

it("Should render add ad modal first stage actions", () => {
const { container } = render(cont);
expect(
container.getElementsByClassName("add-ad-modal-action")
).toBeDefined();
});

it("Should change employment type to intership", () => {
render(cont);
fireEvent.click(screen.getByTestId("add-ad-modal-intership-btn"));
expect(props.setEmploymentType).toHaveBeenCalled();
});

it("Should change employment type to work", () => {
render(cont);
fireEvent.click(screen.getByTestId("add-ad-modal-work-btn"));
expect(props.setEmploymentType).toHaveBeenCalled();
});

it("Should change work hour to parttime", () => {
render(cont);
fireEvent.click(screen.getByTestId("add-ad-modal-parttime-btn"));
expect(props.setWorkHour).toHaveBeenCalled();
});

it("Should change work hour to fulltime", () => {
render(cont);
fireEvent.click(screen.getByTestId("add-ad-modal-fulltime-btn"));
expect(props.setWorkHour).toHaveBeenCalled();
});

it("Should call function on click go back button", async () => {
render(cont);
fireEvent.click(screen.getByTestId("add-ad-modal-go-back"));
waitFor(() => expect(props.onDecrementStage).toHaveBeenCalled());
});

it("Should call function on click go forward button", () => {
render(cont);
fireEvent.click(screen.getByTestId("add-ad-modal-go-forward"));
expect(props.onIncrementStage).toHaveBeenCalled();
});

it("Should change title text", () => {
render(cont);
fireEvent.change(screen.getByTestId("add-ad-modal-title"), { target: { value: ".NET DEVELOPER" } })
expect(props.setTitle).toBeCalled();
});

it("Should change expired at text", () => {
render(cont);
fireEvent.change(screen.getByTestId("add-ad-modal-expired-at"), { target: { value: "2020-05-24" } })
expect(props.setExpiredAt).toBeCalled();
});
});

+ 0
- 73
src/__tests__/UITests/addAdModalSecondStageUI.test.js 파일 보기

@@ -1,73 +0,0 @@
import { render, screen, waitFor, fireEvent } from "@testing-library/react";
import * as redux from "react-redux";
import store from "../../store";
import { Router } from "react-router-dom";
import history from "../../store/utils/history";
import { mockState } from "../../mockState";
import ColorModeProvider from "../../context/ColorModeContext";
import AddAdModalSecondStage from "../../components/Ads/AddAdModalSecondStage";

describe("Add ad modals ui tests", () => {
const props = {
onIncrementStage: jest.fn(),
onDecrementStage: jest.fn(),
technologies: [
{
value: ".NET",
isChecked: false,
technologyId: 1,
technologyType: "Backend",
},
],
experience: 1,
setExperience: jest.fn(),
};

const cont = (
<redux.Provider store={store}>
<Router history={history}>
<ColorModeProvider>
<AddAdModalSecondStage {...props} />
</ColorModeProvider>
</Router>
</redux.Provider>
);

let spyOnUseSelector;

beforeEach(() => {
spyOnUseSelector = jest.spyOn(redux, "useSelector");
spyOnUseSelector.mockReturnValue(mockState.ads.ads);
});

afterEach(() => {
jest.restoreAllMocks();
});

it("Should render add ad modal second stage", () => {
const { container } = render(cont);
expect(
container.getElementsByClassName("add-ad-modal-stages")[0]
).toBeDefined();
});

it("Should change experience input", async () => {
render(cont);
fireEvent.change(screen.getByTestId("add-ad-modal-experience"), {
target: { value: 1 },
});
waitFor(() => expect(props.setExperience).toBeCalled());
});

it("Should call go back", async () => {
render(cont);
fireEvent.click(screen.getByTestId("add-ad-modal-second-go-back"));
waitFor(() => expect(props.onDecrementStage).toBeCalled());
});

it("Should call go forward", async () => {
render(cont);
fireEvent.click(screen.getByTestId("add-ad-modal-second-go-forward"));
waitFor(() => expect(props.onIncrementStage).toBeCalled());
});
});

+ 5
- 1
src/components/Ads/Ad.js 파일 보기

@@ -7,6 +7,7 @@ import linkedin from "../../assets/images/linkedin.png";
import facebook from "../../assets/images/facebook.png";
import instagram from "../../assets/images/instagram.png";
import { selectLogo } from "../../util/helpers/technologiesLogos";
import { useTranslation } from "react-i18next";

const Ad = ({
title,
@@ -18,6 +19,7 @@ const Ad = ({
}) => {
const theme = useTheme();
const matches = useMediaQuery(theme.breakpoints.down("sm"));
const { t } = useTranslation();

return (
<div className={`ad-card ${className}`} onClick={onShowAdDetails}>
@@ -37,7 +39,9 @@ const Ad = ({
</div>

<div className="ad-card-experience">
<p>{minimumExperience}+ years of experience</p>
<p>
{minimumExperience}+ {t("common.experience")}
</p>
</div>

{!matches && (

+ 16
- 4
src/components/Ads/AdDetailsCandidateCard.js 파일 보기

@@ -2,6 +2,7 @@ import React from "react";
import PropTypes from "prop-types";
import { CANDIDATES_DETAILS_PAGE } from "../../constants/pages";
import { useHistory } from "react-router-dom";
import { useTranslation } from "react-i18next";

const AdDetailsCandidateCard = ({
className,
@@ -12,21 +13,32 @@ const AdDetailsCandidateCard = ({
cv,
}) => {
const history = useHistory();
const { t } = useTranslation();
return (
<div data-testid="ad-details-candidate" className={`ad-details-candidate ${className}`}>
<div
data-testid="ad-details-candidate"
className={`ad-details-candidate ${className}`}
>
<div className="ad-details-candidate-date">
<p>{new Date().toLocaleDateString()}</p>
</div>
<div className="ad-details-candidate-title">
<h3 data-testid="ad-details-candidate-title-link" onClick={() => history.push(CANDIDATES_DETAILS_PAGE.replace(":id", id))}>
<h3
data-testid="ad-details-candidate-title-link"
onClick={() =>
history.push(CANDIDATES_DETAILS_PAGE.replace(":id", id))
}
>
{firstName} {lastName}
</h3>
</div>
<div className="ad-details-candidate-experience">
{experience > 0 ? (
<p>{experience}+ years of experience</p>
<p>
{experience}+ {t("common.experience")}
</p>
) : (
<p>No experience</p>
<p>{t("common.noExperience")}</p>
)}
</div>
<div className="ad-details-candidate-buttons">

+ 18
- 12
src/components/Ads/AdFilters.js 파일 보기

@@ -12,6 +12,7 @@ import { changeIsCheckedValue } from "../../store/actions/technologies/technolog
import { useDispatch } from "react-redux";
import { setFilteredAdsReq } from "../../store/actions/ads/adsAction";
import { useHistory } from "react-router-dom";
import { useTranslation } from "react-i18next";

const AdFilters = ({ open, handleClose, technologies }) => {
const [sliderValue, setSliderValue] = useState([0, 10]);
@@ -19,6 +20,7 @@ const AdFilters = ({ open, handleClose, technologies }) => {
const [workHour, setWorkHour] = useState("FullTime");
const dispatch = useDispatch();
const history = useHistory();
const { t } = useTranslation();

const handleSliderChange = (_, newValue) => {
setSliderValue(newValue);
@@ -82,9 +84,9 @@ const AdFilters = ({ open, handleClose, technologies }) => {
<div className="ad-filters-header-container">
<div className="ad-filters-header">
<img src={filterIcon} alt="filter_icon" />
<h3>Filteri</h3>
<h3>{t("filters.filters")}</h3>
<p>
<sub>| Oglasi</sub>
<sub>| {t("ads.ads")}</sub>
</p>
</div>
<div className="ad-filters-header-close" onClick={handleClose}>
@@ -93,7 +95,7 @@ const AdFilters = ({ open, handleClose, technologies }) => {
</div>
<div className="ad-filters-experience">
<div className="ad-filters-sub-title">
<p>Godine iskustva</p>
<p>{t("filters.experience")}</p>
</div>
<div className="ad-filters-experience-slider">
<Slider
@@ -107,7 +109,7 @@ const AdFilters = ({ open, handleClose, technologies }) => {
</div>
<div className="ad-filters-technologies">
<div className="ad-filters-sub-title">
<p>Tehnologije</p>
<p>{t("filters.tecnologies")}</p>
</div>
<div className="ad-filters-technologies-checkboxes">
<FormGroup>
@@ -130,7 +132,7 @@ const AdFilters = ({ open, handleClose, technologies }) => {
</div>
<div className="ad-filters-technologies">
<div className="ad-filters-sub-title">
<p>Tip zaposlenja</p>
<p>{t("filters.employmentType")}</p>
</div>
<div className="ad-filters-employment-type">
<button
@@ -141,7 +143,7 @@ const AdFilters = ({ open, handleClose, technologies }) => {
}`}
onClick={employmentTypeHandler.bind(this, "Intership")}
>
Intership
{t("filters.internship")}
</button>
<button
className={`c-btn ${
@@ -151,13 +153,13 @@ const AdFilters = ({ open, handleClose, technologies }) => {
}`}
onClick={employmentTypeHandler.bind(this, "Work")}
>
Posao
{t("filters.work")}
</button>
</div>
</div>
<div className="ad-filters-technologies">
<div className="ad-filters-sub-title">
<p>Radno vreme</p>
<p>{t("filters.workHour")}</p>
</div>
<div className="ad-filters-employment-type">
<button
@@ -168,7 +170,7 @@ const AdFilters = ({ open, handleClose, technologies }) => {
}`}
onClick={workHourHandler.bind(this, "PartTime")}
>
Part-time
{t("filters.partTime")}
</button>
<button
className={`c-btn ${
@@ -178,13 +180,17 @@ const AdFilters = ({ open, handleClose, technologies }) => {
}`}
onClick={workHourHandler.bind(this, "FullTime")}
>
Full-time
{t("filters.fullTime")}
</button>
</div>
</div>
<div className="ad-filters-search">
<button onClick={onSubmitFilters} className="c-btn c-btn--primary" data-testid="ad-filters-submit">
Pretrazi
<button
onClick={onSubmitFilters}
className="c-btn c-btn--primary"
data-testid="ad-filters-submit"
>
{t("filters.search")}
</button>
</div>
</div>

+ 0
- 163
src/components/Ads/AddAdModal.js 파일 보기

@@ -1,163 +0,0 @@
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 {
resetIsCheckedAddAdValue,
setTechnologiesAddAdReq,
} from "../../store/actions/addAdTechnologies/addAdTechnologiesActions";
import { setCreateAdReq } from "../../store/actions/createAd/createAdActions";

const style = {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 400,
bgcolor: "background.paper",
border: "2px solid #000",
boxShadow: 24,
};
/* istanbul ignore file */
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 technologies = useSelector(
(state) => state.addAdTechnologies.technologies
);
const dispatch = useDispatch();

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

const onSuccessAddAd = () => {
setStage(1);
setTitle("");
setEmploymentType("Work");
setWorkHour("FullTime");
setExpiredAt("");
setExperience(0);
dispatch(resetIsCheckedAddAdValue());
};

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

dispatch(
setCreateAdReq({
title,
minimumExperience: experience,
createdAt: new Date(),
expiredAt,
keyResponsibilities: htmlKeyResponsibilities,
requirements: htmlRequirements,
offer: htmlOffer,
workHour,
employmentType,
technologiesIds,
onSuccessAddAd,
})
);
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}
onClose={handleClose}
aria-labelledby="parent-modal-title"
aria-describedby="parent-modal-description"
>
<Box
sx={{ ...style, width: 512 }}
className="add-ad-modal"
data-testid="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}
onAddAd={addAdHandler}
/>
)}
</div>
</Box>
</Modal>
);
};

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

export default AddAdModal;

+ 0
- 151
src/components/Ads/AddAdModalFirstStage.js 파일 보기

@@ -1,151 +0,0 @@
import React from "react";
import PropTypes from "prop-types";
/* istanbul ignore file */
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
data-testid="add-ad-modal-title"
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
data-testid="add-ad-modal-work-btn"
className={`c-btn ${
employmentType === "Work"
? "c-btn c-btn--primary"
: "c-btn--primary-outlined"
}`}
onClick={employmentTypeHandler.bind(this, "Work")}
>
Posao
</button>
<button
data-testid="add-ad-modal-intership-btn"
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
data-testid="add-ad-modal-parttime-btn"
className={`c-btn ${
workHour === "PartTime"
? "c-btn c-btn--primary"
: "c-btn--primary-outlined"
}`}
onClick={workHourHandler.bind(this, "PartTime")}
>
Part-time
</button>
<button
data-testid="add-ad-modal-fulltime-btn"
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
data-testid="add-ad-modal-expired-at"
type="date"
placeholder="ex"
value={expiredAt}
onChange={(e) => setExpiredAt(e.target.value)}
/>
</div>
</div>

<div className="add-ad-modal-actions">
<button
data-testid="add-ad-modal-go-back"
className="c-btn c-btn--primary-outlined"
disabled
onClick={onDecrementStage}
>
NAZAD
</button>
<button
className="c-btn c-btn--primary"
onClick={completeStageHandler}
data-testid="add-ad-modal-go-forward"
>
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;

+ 0
- 143
src/components/Ads/AddAdModalSecondStage.js 파일 보기

@@ -1,143 +0,0 @@
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}
className="add-ad-modal-technology"
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}
data-testid="add-ad-modal-experience"
onChange={(e) => setExperience(e.target.value)}
/>
</div>
</div>

<div className="add-ad-modal-actions">
<button
className="c-btn c-btn--primary-outlined"
data-testid="add-ad-modal-second-go-back"
onClick={onDecrementStage}
>
NAZAD
</button>
<button
className="c-btn c-btn--primary"
data-testid="add-ad-modal-second-go-forward"
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;

+ 0
- 77
src/components/Ads/AddAdModalThirdStage.js 파일 보기

@@ -1,77 +0,0 @@
import React, { useRef } from "react";
import PropTypes from "prop-types";
import { Editor } from "@tinymce/tinymce-react";

/* istanbul ignore file */
const AddAdModalThirdStage = ({ onDecrementStage, onAddAd }) => {
const editorKeyResponsibilitiesRef = useRef();
const editorRequirementsRef = useRef();
const editorOfferRef = useRef();

const completeStageHandler = () => {
if (
editorKeyResponsibilitiesRef.current.getContent() === "" ||
editorRequirementsRef.current.getContent() === "" ||
editorOfferRef.current.getContent() === ""
) {
return;
}

onAddAd(
editorKeyResponsibilitiesRef.current.getContent(),
editorRequirementsRef.current.getContent(),
editorOfferRef.current.getContent()
);
};

return (
<div className="add-ad-modal-stage">
<div>
<div className="add-ad-modal-stage-sub-card-third">
<label>Glavna zaduzenja</label>
<Editor
onInit={(evt, editor) =>
(editorKeyResponsibilitiesRef.current = editor)
}
style={{ height: "1rem !important" }}
/>
</div>

<div className="add-ad-modal-stage-sub-card-third">
<label>Uslovi</label>
<Editor
onInit={(evt, editor) => (editorRequirementsRef.current = editor)}
style={{ height: "1rem !important" }}
/>
</div>

<div className="add-ad-modal-stage-sub-card-third">
<label>Sta nudimo</label>
<Editor
onInit={(evt, editor) => (editorOfferRef.current = editor)}
style={{ height: "1rem !important" }}
/>
</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,
onAddAd: PropTypes.func,
};

export default AddAdModalThirdStage;

+ 5
- 2
src/components/Ads/ApplyForAd.js 파일 보기

@@ -14,6 +14,7 @@ import ApplyForAdFourthStage from "./ApplyForAdFourthStage";
import { applyForAdReq } from "../../store/actions/applyForAd/applyForAdActions";
import { useHistory } from "react-router-dom";
import { ADS_PAGE } from "../../constants/pages";
import { useTranslation } from "react-i18next";

const ApplyForAd = ({ open, title, adId, onCloseModal }) => {
const [stage, setStage] = useState(1);
@@ -24,7 +25,8 @@ const ApplyForAd = ({ open, title, adId, onCloseModal }) => {
const [phoneNumber, setPhoneNumber] = useState("");
const [mappedTechnologies, setMappedTechnologies] = useState([]);
const [experience, setExperience] = useState(0);
const [professionalQualification, setProfessionalQualification] = useState("");
const [professionalQualification, setProfessionalQualification] =
useState("");
const [linkedinLink, setLinkedinLink] = useState("");
const [githubLink, setGithubLink] = useState("");
const [email, setEmail] = useState("");
@@ -35,6 +37,7 @@ const ApplyForAd = ({ open, title, adId, onCloseModal }) => {
const technologies = useSelector(selectTechnologies);
const dispatch = useDispatch();
const history = useHistory();
const { t } = useTranslation();

useEffect(() => {
dispatch(setTechnologiesReq());
@@ -95,7 +98,7 @@ const ApplyForAd = ({ open, title, adId, onCloseModal }) => {
<img src={briefcaseIcon} alt="plus" />
</div>
<div className="apply-for-ad-header-left-image-title">
<p>Prijavi se</p>
<p>{t("ads.signUp")}</p>
</div>
<div className="apply-for-ad-header-left-image-title-sub">
<sub> | {title}</sub>

+ 17
- 11
src/components/Ads/ApplyForAdFirstStage.js 파일 보기

@@ -1,5 +1,6 @@
import React from "react";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";

const ApplyForAdFirstStage = ({
firstName,
@@ -21,10 +22,12 @@ const ApplyForAdFirstStage = ({
phoneNumber.length === 0 ||
gender.length === 0;

const { t } = useTranslation();

return (
<div>
<div className="apply-for-ad-modal-form-control">
<label>Ime</label>
<label>{t("common.name")}</label>
<input
type="text"
placeholder="ex. Petar"
@@ -34,7 +37,7 @@ const ApplyForAdFirstStage = ({
/>
</div>
<div className="apply-for-ad-modal-form-control">
<label>Prezime</label>
<label>{t("common.lastName")}</label>
<input
type="text"
placeholder="ex. Petrovic"
@@ -44,7 +47,7 @@ const ApplyForAdFirstStage = ({
/>
</div>
<div className="apply-for-ad-modal-form-control">
<label>Pol</label>
<label>{t("common.gender")}</label>
<div style={{ display: "flex" }}>
<div style={{ display: "flex" }}>
<input
@@ -55,7 +58,7 @@ const ApplyForAdFirstStage = ({
checked={gender === "Muski"}
onChange={(e) => setGender(e.target.value)}
/>
<p style={{ marginLeft: "5px" }}>Muski</p>
<p style={{ marginLeft: "5px" }}>{t("common.male")}</p>
</div>
<div style={{ display: "flex", marginLeft: "50px" }}>
<input
@@ -66,12 +69,12 @@ const ApplyForAdFirstStage = ({
checked={gender === "Zenski"}
onChange={(e) => setGender(e.target.value)}
/>
<p style={{ marginLeft: "5px" }}>Zenski</p>
<p style={{ marginLeft: "5px" }}>{t("common.female")}</p>
</div>
</div>
</div>
<div className="apply-for-ad-modal-form-control">
<label>Datum Rodjenja</label>
<label>{t("common.dateOfBirth")}</label>
<input
type="date"
placeholder="ex. Datum rodjenja"
@@ -81,7 +84,7 @@ const ApplyForAdFirstStage = ({
/>
</div>
<div className="apply-for-ad-modal-form-control">
<label>Broj telefona</label>
<label>{t("common.phoneNumber")}</label>
<input
type="text"
placeholder="ex. +381/60/000/0000"
@@ -91,10 +94,13 @@ const ApplyForAdFirstStage = ({
/>
</div>
<div className="apply-for-ad-buttons">
<button disabled>NAZAD</button>
<button disabled={disabled}
data-testid="apply-for-ad-modal-go-forward-button" onClick={() => onIncreaseStage()}>
NASTAVI
<button disabled>{t("common.back")}</button>
<button
disabled={disabled}
data-testid="apply-for-ad-modal-go-forward-button"
onClick={() => onIncreaseStage()}
>
{t("common.continue")}
</button>
</div>
</div>

+ 8
- 6
src/components/Ads/ApplyForAdFourthStage.js 파일 보기

@@ -1,6 +1,7 @@
import React, { useState } from "react";
import PropTypes from "prop-types";
import uploadIcon from "../../assets/images/upload.png";
import { useTranslation } from "react-i18next";

const ApplyForAdFourthStage = ({
coverLetter,
@@ -11,6 +12,7 @@ const ApplyForAdFourthStage = ({
onFinishedFourStages,
}) => {
const [dropzoneActive, setDropzoneActive] = useState(false);
const {t} = useTranslation()

const disabled = pdfFile === null || coverLetter === "";

@@ -46,7 +48,7 @@ const ApplyForAdFourthStage = ({
) : (
<>
<p>
Prevuci .pdf dokument u ovom delu ekrana ili &nbsp;
{t("ads.dragPdf1")} &nbsp;
<label
htmlFor="upload-file"
style={{
@@ -55,9 +57,9 @@ const ApplyForAdFourthStage = ({
color: "#1E92D0",
}}
>
Pretrazi
{t("common.search")}
</label>
&nbsp;na racunaru
&nbsp;{t("ads.dragPdf2")}
</p>
<input
type="file"
@@ -78,7 +80,7 @@ const ApplyForAdFourthStage = ({
</div>
</div>
<div className="apply-for-ad-modal-form-control">
<label>Propratno pismo (Opciono)</label>
<label>{t("ads.coverLetter")}</label>
<textarea
value={coverLetter}
onChange={(e) => setCoverLetter(e.target.value)}
@@ -92,14 +94,14 @@ const ApplyForAdFourthStage = ({
onClick={onDecreaseStage}
data-testid="apply-for-ad-modal-fourth-stage-go-back-button"
>
NAZAD
{t("common.back")}
</button>
<button
disabled={disabled}
data-testid="apply-for-ad-modal-fourth-stage-go-forward-button"
onClick={onFinishedFourStages}
>
PRIJAVI SE
{t("ads.signUp")}
</button>
</div>
</div>

+ 8
- 6
src/components/Ads/ApplyForAdSecondStage.js 파일 보기

@@ -1,6 +1,7 @@
import React from "react";
import PropTypes from "prop-types";
import { Checkbox, FormControlLabel } from "@mui/material";
import { useTranslation } from "react-i18next";

const ApplyForAdSecondStage = ({
professionalQualification,
@@ -12,6 +13,7 @@ const ApplyForAdSecondStage = ({
onIncreaseStage,
onDecreaseStage,
}) => {
const {t} = useTranslation()
let disabled = true;
let isTechnologySelected = false;
if (technologies.length > 0) {
@@ -35,7 +37,7 @@ const ApplyForAdSecondStage = ({
return (
<div data-testid="apply-for-ad-second-stage">
<div className="apply-for-ad-header-title">
<p>Strucna sprema</p>
<p>{t("ads.professionalQualification")}</p>
</div>
<div className="apply-for-ad-modal-form-control">
<input
@@ -47,7 +49,7 @@ const ApplyForAdSecondStage = ({
/>
</div>
<div className="apply-for-ad-header-title">
<p>Tehnologije koje znaš</p>
<p>{t("ads.technologies")}</p>
</div>
<div className="apply-for-ad-header-sub">
<div className="apply-for-ad-header-sub-group">
@@ -93,7 +95,7 @@ const ApplyForAdSecondStage = ({
</div>
</div>
<div className="apply-for-ad-header-sub-group">
<label>Others</label>
<label>{t("ads.others")}</label>
<div className="apply-for-ad-header-sub-group-checkboxes">
{technologies
.filter((x) => x.technologyType === "Other")
@@ -115,7 +117,7 @@ const ApplyForAdSecondStage = ({
</div>
</div>
<div className="apply-for-ad-modal-form-control">
<label>Godine iskustva</label>
<label>{t("filters.experience")}</label>
<input
type="number"
placeholder="ex. 3 godine iskustva"
@@ -125,9 +127,9 @@ const ApplyForAdSecondStage = ({
/>
</div>
<div className="apply-for-ad-buttons">
<button onClick={onDecreaseStage}>NAZAD</button>
<button onClick={onDecreaseStage}>{t("common.back")}</button>
<button onClick={onIncreaseStage} disabled={disabled}>
NASTAVI
{t("common.continue")}
</button>
</div>
</div>

+ 22
- 7
src/components/Ads/ApplyForAdThirdStage.js 파일 보기

@@ -1,5 +1,6 @@
import React from "react";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";

const ApplyForAdThirdStage = ({
onIncreaseStage,
@@ -18,10 +19,12 @@ const ApplyForAdThirdStage = ({
githubLink.length === 0 ||
bitBucketLink.length === 0;

const {t} = useTranslation()

return (
<div>
<div className="apply-for-ad-header-title">
<p>Društvene mreže</p>
<p>{t("common.socialNetwork")}</p>
</div>
<div className="apply-for-ad-modal-form-control">
<label>LinkedIn</label>
@@ -37,7 +40,8 @@ const ApplyForAdThirdStage = ({
<label>GitHub</label>
<input
type="text"
value={githubLink}data-testid="apply-for-ad-modal-third-stage-github-input"
value={githubLink}
data-testid="apply-for-ad-modal-third-stage-github-input"
onChange={(e) => setGithubLink(e.target.value)}
placeholder="ex. https://www.github.com/petarpetrovic"
/>
@@ -46,7 +50,8 @@ const ApplyForAdThirdStage = ({
<label>BitBucket</label>
<input
type="text"
value={bitBucketLink}data-testid="apply-for-ad-modal-third-stage-bitbucket-input"
value={bitBucketLink}
data-testid="apply-for-ad-modal-third-stage-bitbucket-input"
onChange={(e) => setBitBucketLink(e.target.value)}
placeholder="ex. https://developer.atlassian.com/user/petarapetrovic"
/>
@@ -55,15 +60,25 @@ const ApplyForAdThirdStage = ({
<label>Email</label>
<input
type="email"
value={email}data-testid="apply-for-ad-modal-third-stage-email-input"
value={email}
data-testid="apply-for-ad-modal-third-stage-email-input"
onChange={(e) => setEmail(e.target.value)}
placeholder="ex. petar.petrovic@dilig.net"
/>
</div>
<div className="apply-for-ad-buttons">
<button onClick={onDecreaseStage}data-testid="apply-for-ad-modal-third-stage-go-back-button">NAZAD</button>
<button onClick={onIncreaseStage} disabled={disabled}data-testid="apply-for-ad-modal-third-stage-go-forward-button">
NASTAVI
<button
onClick={onDecreaseStage}
data-testid="apply-for-ad-modal-third-stage-go-back-button"
>
{t("common.back")}
</button>
<button
onClick={onIncreaseStage}
disabled={disabled}
data-testid="apply-for-ad-modal-third-stage-go-forward-button"
>
{t("common.continue")}
</button>
</div>
</div>

+ 6
- 2
src/components/Ads/ArchiveAd.js 파일 보기

@@ -1,6 +1,7 @@
import React from "react";
import PropTypes from "prop-types";
import { selectLogo } from "../../util/helpers/technologiesLogos";
import { useTranslation } from "react-i18next";

const ArchiveAd = ({
className,
@@ -10,6 +11,7 @@ const ArchiveAd = ({
expiredAt,
onShowAdDetails,
}) => {
const { t } = useTranslation();
return (
<div className={`archive-ad ${className}`} onClick={onShowAdDetails}>
<div className="archive-ad-date">
@@ -26,9 +28,11 @@ const ArchiveAd = ({
</div>
<div className="archive-ad-experience">
{minimumExperience > 0 ? (
<p>{minimumExperience}+ years of experience</p>
<p>
{minimumExperience}+ {t("common.experience")}
</p>
) : (
<p>No experience needed</p>
<p>{t("ads.noExperience")}</p>
)}
</div>
</div>

+ 5
- 3
src/components/Ads/StatsAd.js 파일 보기

@@ -1,6 +1,7 @@
import React from "react";
import PropTypes from "prop-types";
import { selectLogo } from "../../util/helpers/technologiesLogos";
import { useTranslation } from "react-i18next";

const StatsAd = ({
className,
@@ -11,10 +12,11 @@ const StatsAd = ({
expiredAt,
onShowAdDetails,
}) => {
const {t} = useTranslation()
return (
<div className={`archive-ad stats-ad ${className}`} onClick={onShowAdDetails}>
<div className="ad-count">
{count} prijavljenih
{count} {t("ads.registered")}
</div>
<div className="archive-ad-date">
<p>
@@ -30,9 +32,9 @@ const StatsAd = ({
</div>
<div className="archive-ad-experience">
{minimumExperience > 0 ? (
<p>{minimumExperience}+ years of experience</p>
<p>{minimumExperience}+ {t("common.experience")}</p>
) : (
<p>No experience needed</p>
<p>{t("ads.noExperience2")}</p>
)}
</div>
</div>

+ 3
- 1
src/components/Button/EditButton.js 파일 보기

@@ -4,17 +4,19 @@ import React from "react";
import userPageBtnIcon from "../../assets/images/userPageBtnIcon.png";
import { useTheme } from "@mui/system";
import { useMediaQuery } from "@mui/material";
import { useTranslation } from "react-i18next";

const EditButton = ({ onEnableEdit }) => {
const theme = useTheme();
const matches = useMediaQuery(theme.breakpoints.down("sm"));
const {t} = useTranslation()

return (
<IconButton
className={"c-btn--primary-outlined edit-btn-class c-btn userPageBtn ml-20px no-padding"}
onClick={onEnableEdit}
>
{!matches && "Režim uređivanja"}
{!matches && t("patterns.editing2")}
<img
style={{
position: "relative",

+ 6
- 2
src/components/Button/FilterButton.js 파일 보기

@@ -4,17 +4,21 @@ import React from "react";
import filters from "../../assets/images/filters.png";
import { useTheme } from "@mui/system";
import { useMediaQuery } from "@mui/material";
import { useTranslation } from "react-i18next";

const FilterButton = ({ onShowFilters }) => {
const theme = useTheme();
const matches = useMediaQuery(theme.breakpoints.down("sm"));
const { t } = useTranslation();

return (
<IconButton
className={"c-btn--primary-outlined ads-page-btn fltr-btn c-btn userPageBtn ml-20px no-padding custom-filter-button"}
className={
"c-btn--primary-outlined ads-page-btn fltr-btn c-btn userPageBtn ml-20px no-padding custom-filter-button"
}
onClick={onShowFilters}
>
{!matches && "Filteri"}
{!matches && t("filters.filters")}
<img
style={{
position: "relative",

+ 4
- 2
src/components/Candidates/CandidateCard.js 파일 보기

@@ -2,8 +2,10 @@ import React from "react";
import PropTypes from "prop-types";
import { formatDate } from "../../util/helpers/dateHelpers";
import { CANDIDATES_PAGE } from "../../constants/pages";
import { useTranslation } from "react-i18next";

const CandidateCard = ({ candidate, className, history }) => {
const { t } = useTranslation();
const navigateToDetailsPage = () => {
history.push({
pathname: CANDIDATES_PAGE + "/" + candidate.applicantId,
@@ -27,8 +29,8 @@ const CandidateCard = ({ candidate, className, history }) => {
</p>
<p className="candidate-card-years">
{candidate.experience === 0
? "No experience"
: candidate.experience + "+ years of experience"}
? t("common.noExperience")
: candidate.experience + " " + t("common.experience")}
</p>
<div className="candidate-card-tecnologies-container">
{candidate.technologyApplicants.map((technology, index) => (

+ 12
- 11
src/components/Candidates/CandidateFilters.js 파일 보기

@@ -18,6 +18,7 @@ import {
changeIsCheckedValue,
resetIsCheckedValue,
} from "../../store/actions/technologies/technologiesActions";
import { useTranslation } from "react-i18next";

const CandidateFilters = ({
open,
@@ -37,9 +38,9 @@ const CandidateFilters = ({
}) => {
const dispatch = useDispatch();
const [isInitial, setIsInitial] = useState(true);
const { t } = useTranslation();

useEffect(() => {
}, [isTableView]);
useEffect(() => {}, [isTableView]);

const handleSliderChange = (_, newValue) => {
setSliderValue(newValue);
@@ -184,9 +185,9 @@ const CandidateFilters = ({
<div className="ad-filters-header-container">
<div className="ad-filters-header">
<img src={filterIcon} alt="filter_icon" />
<h3>Filteri</h3>
<h3>{t("filters.filters")}</h3>
<p>
<sub>| Kandidati</sub>
<sub>| {t("candidates.candidates")}</sub>
</p>
</div>
<div className="ad-filters-header-close" onClick={handleClose}>
@@ -195,7 +196,7 @@ const CandidateFilters = ({
</div>
<div className="ad-filters-experience">
<div className="ad-filters-sub-title">
<p>Godine iskustva</p>
<p>{t("filters.experience")}</p>
</div>
<div className="ad-filters-experience-slider">
<Slider
@@ -209,7 +210,7 @@ const CandidateFilters = ({
</div>
<div className="ad-filters-technologies">
<div className="ad-filters-sub-title">
<p>Tehnologije</p>
<p>{t("filters.technologies")}</p>
</div>
<div className="ad-filters-technologies-checkboxes">
<FormGroup>
@@ -232,7 +233,7 @@ const CandidateFilters = ({
</div>
<div className="ad-filters-technologies">
<div className="ad-filters-sub-title">
<p>Tip zaposlenja</p>
<p>{t("filters.employmentType")}</p>
</div>
<div className="ad-filters-employment-type">
{typesOfEmployments.map((type, index) => (
@@ -252,9 +253,9 @@ const CandidateFilters = ({
</div>
<div className="ad-filters-technologies" style={{ marginTop: "35px" }}>
<div className="ad-filters-sub-title">
<p>Datum prijave</p>
<p>{t("filters.dateOfApplication")}</p>
<div className="filter-date-container">
<p>Od</p>
<p>{t("common.from")}</p>
<input
type="date"
id="start"
@@ -264,7 +265,7 @@ const CandidateFilters = ({
/>
</div>
<div className="filter-date-container">
<p>Do</p>
<p>{t("common.to")}</p>
<input
type="date"
id="start"
@@ -282,7 +283,7 @@ const CandidateFilters = ({
onClick={fiterItems}
disabled={!isThereSelectedFilter()}
>
Pretrazi
{t("common.search")}
</button>
</div>
</div>

+ 4
- 2
src/components/MUI/CommentProcessDialog.js 파일 보기

@@ -20,6 +20,7 @@ import { useDispatch, useSelector } from "react-redux";
import { useEffect } from "react";
import { SelectionContext } from "../../context/SelectionContext";
import { setUpdateStatusReq } from "../../store/actions/processes/processAction";
import { useTranslation } from "react-i18next";
// import { setUpdateInterviewerReq } from "../../store/actions/processes/processAction";
// import { setUpdateStatusReq } from "../../store/actions/processes/processAction";

@@ -34,6 +35,7 @@ const CommentProcessDialog = ({
responsive,
}) => {
const { activeProcessUnsuccess, setActiveProcessUnsuccess } = useContext(SelectionContext);
const {t} = useTranslation()

const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down("md"));
@@ -151,7 +153,7 @@ const CommentProcessDialog = ({
className={`c-btn--primary-outlined interview-btn c-btn dialog-btn`}
onClick={onClose}
>
Otkaži
{t("common.cancel")}
</IconButton>
) : (
""
@@ -161,7 +163,7 @@ const CommentProcessDialog = ({
className={`c-btn--primary-outlined sm-full interview-btn c-btn dialog-btn`}
onClick={submitHandler}
>
Potvrdi
{t("common.confirm")}
</IconButton>
</DialogActions>
</div>

+ 15
- 9
src/components/MUI/ConfirmDialog.js 파일 보기

@@ -10,6 +10,7 @@ import {
DialogContent,
} from "@mui/material";
import IconButton from "../IconButton/IconButton";
import { useTranslation } from "react-i18next";

const ConfirmDialog = ({
title,
@@ -25,6 +26,7 @@ const ConfirmDialog = ({
}) => {
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down("md"));
const { t } = useTranslation();

const handleClose = () => {
onClose();
@@ -41,11 +43,15 @@ const ConfirmDialog = ({
padding: "36px",
}}
>
<div style={{ padding: "36px" }} data-testid='alert-container'>
<div style={{ padding: "36px" }} data-testid="alert-container">
<DialogTitle style={{ padding: 0 }}>
{fullScreen ? (
<>
<div className="flex-center" style={{ justifyContent: "start" }} data-testid="full-screen-elem">
<div
className="flex-center"
style={{ justifyContent: "start" }}
data-testid="full-screen-elem"
>
<img
style={{
position: "relative",
@@ -55,10 +61,8 @@ const ConfirmDialog = ({
src={imgSrc}
/>
<h5 style={{ textAlign: "start" }}>{title}</h5>
<div style={{ justifySelf: "stretch", flex:'1' }}></div>
<IconButton
onClick={onClose}
>
<div style={{ justifySelf: "stretch", flex: "1" }}></div>
<IconButton onClick={onClose}>
<img
style={{
position: "relative",
@@ -99,7 +103,9 @@ const ConfirmDialog = ({
)}
</DialogTitle>
<DialogContent>
<div className="modal-content" data-testid="modal-content">{content}</div>
<div className="modal-content" data-testid="modal-content">
{content}
</div>
</DialogContent>
<DialogActions style={{ padding: 0 }}>
{!fullScreen ? (
@@ -107,7 +113,7 @@ const ConfirmDialog = ({
className={`c-btn--primary-outlined c-btn dialog-btn not-full-screen-btn`}
onClick={onClose}
>
Otkaži
{t("common.cancel")}
</IconButton>
) : (
""
@@ -116,7 +122,7 @@ const ConfirmDialog = ({
className={`c-btn--primary-outlined sm-full c-btn dialog-btn`}
onClick={onConfirm}
>
Potvrdi
{t("common.confirm")}
</IconButton>
</DialogActions>
</div>

+ 6
- 4
src/components/MUI/InterviewDialog.js 파일 보기

@@ -21,6 +21,7 @@ import { useDispatch, useSelector } from "react-redux";
import { format, isValid } from "date-fns";
import { fetchInitProcess } from "../../store/actions/candidates/candidatesActions";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";

const InterviewDialog = ({
title,
@@ -35,6 +36,7 @@ const InterviewDialog = ({
const [selected, setSelected] = useState("");
const [selectedInterviewer, setSelectedInterviewer] = useState(null);
const [value, setValue] = useState(null);
const { t } = useTranslation();

const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down("md"));
@@ -148,7 +150,7 @@ const InterviewDialog = ({
<form className="modal-content interviewDialog">
<FormControl fullWidth>
<InputLabel id="demo-simple-select-label">
Ime kandidata
{t("dialogs.candidateName")}
</InputLabel>
<Select
labelId="demo-simple-select-label"
@@ -176,7 +178,7 @@ const InterviewDialog = ({
</FormControl>
<FormControl fullWidth>
<InputLabel id="demo-simple-select-label">
Ime intervjuera (opciono)
{t("dialogs.interviewerName")}
</InputLabel>
<Select
labelId="demo-simple-select-label"
@@ -215,7 +217,7 @@ const InterviewDialog = ({
className={`c-btn--primary-outlined interview-btn c-btn dialog-btn`}
onClick={onClose}
>
Otkaži
{t("common.cancel")}
</IconButton>
) : (
""
@@ -225,7 +227,7 @@ const InterviewDialog = ({
className={`c-btn--primary-outlined sm-full interview-btn c-btn dialog-btn`}
onClick={submitHandler}
>
Potvrdi
{t("common.confirm")}
</IconButton>
</DialogActions>
</div>

+ 7
- 8
src/components/MUI/InterviewerDialog.js 파일 보기

@@ -23,6 +23,7 @@ import { useDispatch, useSelector } from "react-redux";
import { useEffect } from "react";
import { SelectionContext } from "../../context/SelectionContext";
import { setUpdateInterviewerReq } from "../../store/actions/processes/processAction";
import { useTranslation } from "react-i18next";
// import { setUpdateStatusReq } from "../../store/actions/processes/processAction";

const InterviewerDialog = ({
@@ -35,11 +36,9 @@ const InterviewerDialog = ({
fullWidth,
responsive,
}) => {
const {
activeInterview,
setActiveInterview
} = useContext(SelectionContext);
const { activeInterview, setActiveInterview } = useContext(SelectionContext);
const [selected, setSelected] = useState("");
const {t} = useTranslation()

const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down("md"));
@@ -74,7 +73,7 @@ const InterviewerDialog = ({
};

const apiSuccess = () => {
setActiveInterview(null)
setActiveInterview(null);
// console.log("ok");
};

@@ -148,7 +147,7 @@ const InterviewerDialog = ({
<form className="modal-content interviewDialog">
<FormControl fullWidth>
<InputLabel id="demo-simple-select-label">
Ime intervjuera
{t("dialogs.interviewerName2")}
</InputLabel>
<Select
labelId="demo-simple-select-label"
@@ -188,7 +187,7 @@ const InterviewerDialog = ({
className={`c-btn--primary-outlined interview-btn c-btn dialog-btn`}
onClick={onClose}
>
Otkaži
{t("common.cancel")}
</IconButton>
) : (
""
@@ -198,7 +197,7 @@ const InterviewerDialog = ({
className={`c-btn--primary-outlined sm-full interview-btn c-btn dialog-btn`}
onClick={submitHandler}
>
Potvrdi
{t("common.confirm")}
</IconButton>
</DialogActions>
</div>

+ 4
- 4
src/components/MUI/InviteDialog.js 파일 보기

@@ -106,7 +106,7 @@ const InviteDialog = ({
inputProps={{ "data-testid": "invite-input-text" }}
name="firstName"
// label={t("users.receiver")}
label={"Ime"}
label={t("common.firstName")}
margin="normal"
value={formik.values.firstName}
onChange={formik.handleChange}
@@ -120,7 +120,7 @@ const InviteDialog = ({
inputProps={{ "data-testid": "invite-input-text" }}
name="lastName"
// label={t("users.receiver")}
label={"Prezime"}
label={t("common.lastName")}
margin="normal"
value={formik.values.lastName}
onChange={formik.handleChange}
@@ -135,7 +135,7 @@ const InviteDialog = ({
inputProps={{ "data-testid": "invite-input-text" }}
name="email"
// label={t("users.receiver")}
label={"E-mail adresa"}
label={"E-mail " + t("common.address")}
margin="normal"
value={formik.values.email}
onChange={formik.handleChange}
@@ -160,7 +160,7 @@ const InviteDialog = ({
}}
src={planeVector}
/>{" "}
Registracioni link
{t("registration.link")}
</IconButton>
</form>
</DialogContent>

+ 2
- 2
src/components/MUI/NavbarComponent.js 파일 보기

@@ -97,7 +97,7 @@ const NavbarComponent = () => {
<Typography
sx={{ fontWeight: 600, fontSize: "18px", letterSpacing: "1.5px" }}
>
Navigacija
{t("nav.navigation")}
</Typography>
{/* <img src={x}/> */}
<IconButton onClick={handleToggleDrawer}>
@@ -118,7 +118,7 @@ const NavbarComponent = () => {
sx={{ fontSize: "14px", marginTop: "8px" }}
className="text-grey9d"
>
HR Specialist
HR {t("common.specialist")}
</Typography>
<div className="hr" style={{ width: "90%", marginTop: "18px" }}></div>
</div>

+ 8
- 6
src/components/MUI/StatusDialog.js 파일 보기

@@ -26,6 +26,7 @@ import {
createScreeningTest,
fetchScreeningTests,
} from "../../store/actions/screeningTests/screeningTestActions";
import { useTranslation } from "react-i18next";

const StatusDialog = ({
title,
@@ -49,6 +50,7 @@ const StatusDialog = ({
const { users } = useSelector((s) => s.users);
const { isSuccess } = useSelector((s) => s.initProcess);
const { screeningTests } = useSelector((s) => s.screeningTests);
const { t } = useTranslation();

const dispatch = useDispatch();

@@ -197,13 +199,13 @@ const StatusDialog = ({
<form className="modal-content interviewDialog">
<FormControl fullWidth>
<InputLabel id="demo-simple-select-label">
Ime intervjuera
{t("dialogs.interviewerName2")}
</InputLabel>
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
value={selected}
label="Ime intervjuera"
label={t("dialogs.interviewerName2")}
onChange={(e) => {
setSelected(e.target.value);
}}
@@ -255,7 +257,7 @@ const StatusDialog = ({
activeProcess.process.selectionLevelId === 2 && (
<TextField
name="duration"
label={"Duration"}
label={t("dialogs.duration")}
value={duration}
onChange={(e) => setDuration(e.target.value)}
fullWidth
@@ -264,7 +266,7 @@ const StatusDialog = ({

{/* {activeProcess.process && activeProcess.process.date ? <p>Proces ima zakazan termin</p> : ''} */}
<DateTimePicker
label="Termin"
label={t("dialogs.appointment")}
value={value}
onChange={handleChange}
renderInput={(params) => <TextField {...params} />}
@@ -278,7 +280,7 @@ const StatusDialog = ({
className={`c-btn--primary-outlined interview-btn c-btn dialog-btn`}
onClick={onClose}
>
Cancel
{t("common.cancel")}
</IconButton>
) : (
""
@@ -288,7 +290,7 @@ const StatusDialog = ({
className={`c-btn--primary-outlined sm-full interview-btn c-btn dialog-btn`}
onClick={submitHandler}
>
Confirm
{t("common.confirm")}
</IconButton>
</DialogActions>
</div>

+ 8
- 6
src/components/Patterns/PatternFilters.js 파일 보기

@@ -4,6 +4,7 @@ import { Checkbox, FormControlLabel, FormGroup } from "@mui/material";
import PropTypes from "prop-types";
import { useDispatch } from "react-redux";
import { setFilteredPatternsReq } from "../../store/actions/patterns/patternsActions";
import { useTranslation } from "react-i18next";

const PatternFilters = ({
openFilterDrawer,
@@ -14,8 +15,9 @@ const PatternFilters = ({
const [fromDate, setFromDate] = useState("");
const [toDate, setToDate] = useState("");
const dispatch = useDispatch();
const { t } = useTranslation();

console.log("OVDE",selectionLevelFilter)
console.log("OVDE", selectionLevelFilter);

const submitFiltersHandler = (e) => {
e.preventDefault();
@@ -69,7 +71,7 @@ const PatternFilters = ({
<div>
<div className="custom-drawer-sub-card">
<div className="custom-drawer-sub-card-label">
<p>Kategorija</p>
<p>{t("filters.category")}</p>
</div>
<div className="custom-drawer-sub-card-content">
<FormGroup>
@@ -92,13 +94,13 @@ const PatternFilters = ({
</div>
<div className="custom-drawer-sub-card">
<div className="custom-drawer-sub-card-label">
<p>Datum kreiranja</p>
<p>{t("filters.creationDate")}</p>
</div>
<div className="custom-drawer-sub-card-content">
<FormGroup style={{ marginBottom: "9px" }}>
<div className="custom-drawer-sub-card-content-sub">
<label className="custom-drawer-sub-card-content-sub-label">
Od
{t("common.from")}
</label>
<input
type="date"
@@ -111,7 +113,7 @@ const PatternFilters = ({
<FormGroup>
<div className="custom-drawer-sub-card-content-sub">
<label className="custom-drawer-sub-card-content-sub-label">
Do
{t("common.to")}
</label>
<input
type="date"
@@ -130,7 +132,7 @@ const PatternFilters = ({
data-testid="custom-drawer-submit-search"
disabled={!hasSelectedFilters()}
>
Pretrazi
{t("common.search")}
</button>
</div>
</div>

+ 1
- 1
src/components/Profile/UserProfile.js 파일 보기

@@ -39,7 +39,7 @@ const UserProfile = ({ show, innerRef }) => {
sx={{ fontSize: "14px", marginTop: "8px" }}
className="text-grey9d"
>
HR Specialist
HR {t("common.specialist")}
</Typography>
{/* <div className="hr" style={{ width: "90%", marginTop: "18px" }}></div> */}


+ 9
- 15
src/components/Registration/FirstStepForm.js 파일 보기

@@ -55,22 +55,16 @@ function FirstStepForm() {
.matches(
// eslint-disable-next-line
/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]+/,
"Paswword must contain atleast one special character"
t("login.specialCharacterRequired")
)
.matches(/[0-9]/g, "Paswword must contain atleast one number")
.matches(
/[a-z]/g,
"Paswword must contain at least one lowercase character"
)
.matches(
/[A-Z]/g,
"Paswword must contain at least one uppercase character"
)
.min(8, "Password must contain at least 8 characters"),
.matches(/[0-9]/g, t("login.numberRequired"))
.matches(/[a-z]/g, t("login.lowercaseRequired"))
.matches(/[A-Z]/g, t("login.uppercaseRequired"))
.min(8, t("login.numberOfCharactersRequired")),
confirm: yup
.string()
.required("Please retype your password.")
.oneOf([yup.ref("password")], "Your passwords do not match."),
.required(t("login.retypePassword"))
.oneOf([yup.ref("password")], t("login.matchPasswords")),
}),
onSubmit: submitHandler,
validateOnBlur: true,
@@ -127,7 +121,7 @@ function FirstStepForm() {
<TextField
className="rounded-input"
name="confirm"
label={"Potvrda šifre"}
label={t("login.passwordConfirmation")}
margin="normal"
type={showPassword ? "text" : "password"}
fullWidth
@@ -156,7 +150,7 @@ function FirstStepForm() {
className="c-btn c-btn--primary w-289 f"
type="submit"
>
Nastavi
{t("common.continue")}
</Button>
{/* <div className="flex-center">
<div className="hr hr-s"></div>

+ 2
- 2
src/components/Registration/SecondStepForm.js 파일 보기

@@ -103,7 +103,7 @@ function SecondStepForm() {
className="c-btn c-btn--primary w-289 f"
type="submit"
>
Registruj se
{t("registration.register")}
</Button>
{/* <div className="flex-center">
<div className="hr hr-s"></div>
@@ -130,4 +130,4 @@ function SecondStepForm() {
);
}

export default SecondStepForm;
export default SecondStepForm;

+ 18
- 15
src/components/Schedules/DayComponent.js 파일 보기

@@ -1,31 +1,34 @@
import React from "react";
import PropTypes from "prop-types";
import { formatTimeSrb } from "../../util/helpers/dateHelpers";
import { useTranslation } from "react-i18next";

const DayComponent = ({ numberOfDay, nameOfDay, interviews, onClick }) => {
const { t } = useTranslation();
return (
<div className="day-component-container" onClick={onClick}>
<div className="day-component-day-informations-container">
<p className="day-component-day-number">{numberOfDay}</p>
<p className="day-component-day-name">{nameOfDay}</p>
</div>
{interviews && interviews.slice(0, 2).map((interview, index) => (
<div
key={index}
className="day-component-interviews-container"
style={{ marginTop: "4px" }}
>
<p className="day-component-interviews-time">
{formatTimeSrb(interview.date)}
</p>
<p className="day-component-interviews-name">
{interview.selectionLevel.name}
</p>
</div>
))}
{interviews &&
interviews.slice(0, 2).map((interview, index) => (
<div
key={index}
className="day-component-interviews-container"
style={{ marginTop: "4px" }}
>
<p className="day-component-interviews-time">
{formatTimeSrb(interview.date)}
</p>
<p className="day-component-interviews-name">
{interview.selectionLevel.name}
</p>
</div>
))}
{interviews && interviews.length > 2 && (
<span className="day-component-more">
+{interviews.length - 2} stavke
+{interviews.length - 2} {t("schedule.items")}
</span>
)}
</div>

+ 4
- 4
src/components/Schedules/DayDetailsComponent.js 파일 보기

@@ -11,6 +11,7 @@ import { formatTimeSrb } from "../../util/helpers/dateHelpers";
import { CANDIDATES_PAGE } from "../../constants/pages";
import { useTheme } from "@emotion/react";
import { useMediaQuery } from "@mui/material";
import { useTranslation } from "react-i18next";

const DayDetailsComponent = ({
selectedDate,
@@ -27,6 +28,7 @@ const DayDetailsComponent = ({
const matches = useMediaQuery(theme.breakpoints.down("361"));
const [isLeftArrowDisabled, setIsLeftArrowDisabled] = useState(false);
const [isRightArrowDisabled, setIsRightArrowDisabled] = useState(false);
const { t } = useTranslation();
const handleClose = () => {
onClose();
};
@@ -85,7 +87,7 @@ const DayDetailsComponent = ({
>
<DialogTitle className="day-datails-title-container">
<img src={calendar} className="day-details-calendar-image" />
<p className="day-details-main-header">Planer aktivnosti</p>
<p className="day-details-main-header">{t("schedule.planner")}</p>
<p className="day-details-header">| {selectedDate}.</p>
<img
src={x}
@@ -153,9 +155,7 @@ const DayDetailsComponent = ({
}}
>
{isLeftArrowDisabled === true ? (
<div
className="day-details-arrow-container"
>
<div className="day-details-arrow-container">
<img src={arrowLeftDisabled} />
</div>
) : (

+ 72
- 58
src/components/Selection/ApplicantSelection.js 파일 보기

@@ -2,76 +2,90 @@ import React from "react";
import PropTypes from "prop-types";
import { formatDate } from "../../util/helpers/dateHelpers";
import { Link } from "react-router-dom";
import success from "../../assets/images/svg/success.svg"
import unsucc from "../../assets/images/unsucc.png"
import success from "../../assets/images/svg/success.svg";
import unsucc from "../../assets/images/unsucc.png";
import { useTranslation } from "react-i18next";

const ApplicantSelection = ({
levelNumber,
levelName,
schedguler,
link,
date,
status,
id,
onShowAdDetails,
className,
levelNumber,
levelName,
schedguler,
link,
date,
status,
id,
onShowAdDetails,
className,
}) => {
return (
<div data-testid='appSelection' className={`active-process-card ${className}`} onClick={onShowAdDetails}>
<div className="active-process-card-header">
<div className="active-process-card-number">
<p>
{levelNumber}
</p>
</div>
{(status === "Odrađen") && <div className="active-process-card-logo">
<img src={success} alt="success-image" />
</div>}
{(status === "Neuspešno") && <div className="active-process-card-logo">
<img src={unsucc} alt="success-image" />
</div>}
</div>
const { t } = useTranslation();
return (
<div
data-testid="appSelection"
className={`active-process-card ${className}`}
onClick={onShowAdDetails}
>
<div className="active-process-card-header">
<div className="active-process-card-number">
<p>{levelNumber}</p>
</div>
{status === "Odrađen" && (
<div className="active-process-card-logo">
<img src={success} alt="success-image" />
</div>
)}
{status === "Neuspešno" && (
<div className="active-process-card-logo">
<img src={unsucc} alt="success-image" />
</div>
)}
</div>

<div className="active-process-card-body">
<div className="active-process-card-date">
<p>
{date && date !== "" && formatDate(new Date(date))}
</p>
</div>
<div className="active-process-card-body">
<div className="active-process-card-date">
<p>{date && date !== "" && formatDate(new Date(date))}</p>
</div>

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

<div className="active-process-card-date">
<p>
{schedguler}
</p>
</div>
<div className="active-process-card-date">
<p>{schedguler}</p>
</div>

<div className="active-process-card-buttons">
<button className={status === "Neuspešno" && 'unsucc' }>{status}</button>
</div>
<div className="active-process-card-buttons">
<button className={status === "Neuspešno" && "unsucc"}>
{status}
</button>
</div>

<div className="active-process-card-link">
{(status === "Odrađen") && <Link className="ad-details-buttons-link" to={"/candidates/" + id}>Detaljni izveštaja</Link>}
{(link && status !== "Odrađen") && <a href={link} className="ad-details-buttons-link">Link do Google Meet-a</a>}
</div>
</div>
<div className="active-process-card-link">
{status === "Odrađen" && (
<Link className="ad-details-buttons-link" to={"/candidates/" + id}>
{t("selection.report")}
</Link>
)}
{link && status !== "Odrađen" && (
<a href={link} className="ad-details-buttons-link">
{t("selection.link")}
</a>
)}
</div>
);
</div>
</div>
);
};

ApplicantSelection.propTypes = {
id: PropTypes.string,
levelNumber: PropTypes.number,
levelName: PropTypes.string,
schedguler: PropTypes.string,
link: PropTypes.string,
date: PropTypes.any,
status: PropTypes.string,
onShowAdDetails: PropTypes.func,
className: PropTypes.any,
id: PropTypes.string,
levelNumber: PropTypes.number,
levelName: PropTypes.string,
schedguler: PropTypes.string,
link: PropTypes.string,
date: PropTypes.any,
status: PropTypes.string,
onShowAdDetails: PropTypes.func,
className: PropTypes.any,
};

export default ApplicantSelection;

+ 16
- 7
src/components/Selection/Selection.js 파일 보기

@@ -12,6 +12,7 @@ import Backdrop from "../../components/MUI/BackdropComponent";
import { IconButton } from "@mui/material";
import plus from "../../assets/images/plus.png";
import SelectionCard from "./SelectionCard";
import { useTranslation } from "react-i18next";

const Selection = (props) => {
const [over, setOver] = useState(false);
@@ -19,13 +20,17 @@ const Selection = (props) => {
const errorMessage = useSelector(selectDoneProcessError);
const dispatch = useDispatch();
const user = useSelector(selectAuthUser);
const { t } = useTranslation();

const dropItem = (e, selId) => {
setOver(false);
var data = e.dataTransfer.getData("text/plain");
const selectionProcess = JSON.parse(data);

if (selectionProcess.selectionLevelId < selId && selectionProcess.status !== "Neuspešno") {
if (
selectionProcess.selectionLevelId < selId &&
selectionProcess.status !== "Neuspešno"
) {
dispatch(
setDoneProcessReq({
id: selectionProcess.id,
@@ -58,8 +63,10 @@ const Selection = (props) => {
props.history.push(SELECTION_PROCESS_OF_APPLICANT_PAGE.replace(":id", id));
};

const applicants = allApplicants?.filter((a) => a.status !== "Odrađen" || a.selectionLevelId === 4);
const applicants = allApplicants?.filter(
(a) => a.status !== "Odrađen" || a.selectionLevelId === 4
);

const renderList = applicants?.map((item, index) => {
return (
<SelectionCard
@@ -82,10 +89,10 @@ const Selection = (props) => {
onDrop={(e) => dropItem(e, props.selection.id)}
>
<div className="selection-card-title">
<h3 style={{marginRight:'50px'}}>{props.selection.name}</h3>
<h3 style={{ marginRight: "50px" }}>{props.selection.name}</h3>
{props.order === 0 ? (
<IconButton
sx={{marginRight:'35px'}}
sx={{ marginRight: "35px" }}
className={`c-btn--primary-outlined c-btn td-btn`}
onClick={props.modalEvent}
>
@@ -94,7 +101,7 @@ const Selection = (props) => {
position: "relative",
}}
src={plus}
data-testid='interview-image'
data-testid="interview-image"
/>
</IconButton>
) : (
@@ -109,7 +116,9 @@ const Selection = (props) => {
{applicants && applicants !== null && applicants?.length === 0 && (
<div className="sel-item-no-data">
<div className="date">
<p data-testid='empty-selection' style={{width:'240px'}}>Nema kandidata u selekciji</p>
<p data-testid="empty-selection" style={{ width: "240px" }}>
{t("selection.noCandidates")}
</p>
</div>
</div>
)}

+ 5
- 3
src/components/Selection/SelectionCard.js 파일 보기

@@ -14,6 +14,7 @@ import {
setDoneProcessReq,
setUpdateStatusReq,
} from "../../store/actions/processes/processAction";
import { useTranslation } from "react-i18next";
// import Button from "../Button/Button";

const options = ["Zakazan", "Odrađen", "Čeka na zakazivanje", "Neuspešno"];
@@ -21,6 +22,7 @@ const options = ["Zakazan", "Odrađen", "Čeka na zakazivanje", "Neuspešno"];
const SelectionCard = (props) => {
const [showForm, setShowForm] = useState(false);
const [selected, setSelected] = useState(props.item.status);
const { t } = useTranslation();

const { success } = useSelector((s) => s.statusUpdate);
const {
@@ -96,7 +98,7 @@ const SelectionCard = (props) => {
className="sel-item"
onDragStart={props.dragStart}
onClick={clickHandler}
data-testid='sel-card'
data-testid="sel-card"
>
{" "}
<div
@@ -170,11 +172,11 @@ const SelectionCard = (props) => {
className="interbtn"
onClick={(e) => changeInterviewerHandler(e)}
>
Promeni
{t("common.change")}
</Button>
</div>
<p>
Intervjuer: {props.item.scheduler.firstName}{" "}
{t("selection.interviewer")}: {props.item.scheduler.firstName}{" "}
{props.item.scheduler.lastName}
</p>
</div>

+ 5
- 5
src/components/Selection/SelectionFilter.js 파일 보기

@@ -56,7 +56,7 @@ const SelectionFilter = ({ open, handleClose, statuses }) => {
<div className="ad-filters-header-container">
<div className="ad-filters-header">
<img src={filterIcon} alt="filter_icon" />
<h3>{t("filter.title")}</h3>
<h3>{t("filters.filters")}</h3>
<p>
<sub>| {t("selection.title")}</sub>
</p>
@@ -67,7 +67,7 @@ const SelectionFilter = ({ open, handleClose, statuses }) => {
</div>
<div className="ad-filters-technologies">
<div className="ad-filters-sub-title">
<p>{t("selection.filterStatus")}</p>
<p>Status</p>
</div>
<div className="ad-filters-technologies-checkboxes">
<FormGroup>
@@ -101,7 +101,7 @@ const SelectionFilter = ({ open, handleClose, statuses }) => {
<p>{t("selection.filterDate")}</p>
</div>
<div className="add-ad-modal-stage-sub-card">
<label>{t("selection.filterFrom")}</label>
<label>{t("common.from")}</label>
<input
type="date"
placeholder="ex"
@@ -110,7 +110,7 @@ const SelectionFilter = ({ open, handleClose, statuses }) => {
/>
</div>
<div className="add-ad-modal-stage-sub-card">
<label>{t("selection.filterTo")}</label>
<label>{t("common.to")}</label>
<input
type="date"
placeholder="ex"
@@ -121,7 +121,7 @@ const SelectionFilter = ({ open, handleClose, statuses }) => {
</div>
<div className="ad-filters-search">
<button onClick={onSubmitFilters} className="c-btn c-btn--primary">
{t("selection.filterSubmit")}
{t("common.search")}
</button>
</div>
</div>

+ 3
- 1
src/components/UI/CustomDrawer.js 파일 보기

@@ -4,8 +4,10 @@ import Box from "@mui/material/Box";
import Drawer from "@mui/material/Drawer";
import filterIcon from "../../assets/images/filters.png";
import x from "../../assets/images/x.png";
import { useTranslation } from "react-i18next";

const CustomDrawer = ({ title, open, onCloseDrawer, children }) => {
const { t } = useTranslation();
const list = () => (
<Box
sx={{
@@ -21,7 +23,7 @@ const CustomDrawer = ({ title, open, onCloseDrawer, children }) => {
<div className="custom-filter-drawer-header-container">
<div className="custom-filter-drawer-header">
<img src={filterIcon} alt="filter_icon" />
<h3>Filteri</h3>
<h3>{t("filters.filters")}</h3>
<p>
<sub>| {title}</sub>
</p>

+ 9
- 9
src/i18n/index.js 파일 보기

@@ -1,21 +1,21 @@
import { format as formatDate } from 'date-fns';
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import { format as formatDate } from "date-fns";
import i18n from "i18next";
import { initReactI18next } from "react-i18next";

import enTranslations from './resources/en';
import rsTranslations from './resources/rs';
import enTranslations from "./resources/en";
import rsTranslations from "./resources/rs";

/* istanbul ignore file */
i18n.use(initReactI18next).init({
lng: 'rs',
fallbackLng: 'en',
lng: "en",
fallbackLng: "en",
debug: false,
supportedLngs: ['en','rs'],
supportedLngs: ["en", "rs"],
resources: {
en: {
translation: enTranslations,
},
rs:{
rs: {
translation: rsTranslations,
},
},

+ 151
- 7
src/i18n/resources/en.js 파일 보기

@@ -11,20 +11,23 @@ export default {
trademark: "TM",
search: "Search",
error: "Error",
continue: "Continue",
continue: "CONTINUE",
labelUsername: "Username",
labelPassword: "Password",
or: "or",
and:"and",
next: "Next",
nextPage: "Next page",
previousPage: "Previous page",
back: "Back",
back: "BACK",
goBack: "Go Back",
ok: "Ok",
done: "Done",
confirm: "Confirm",
printDownload: "Print/Download",
cancel: "Cancel",
delete: "Delete",
change: "Change",
remove: "Remove",
invite: "Invite",
save: "Save",
@@ -32,12 +35,26 @@ export default {
download: "Download",
yes: "Yes",
no: "No",
to: "to",
to: "To",
from: "From",
select: "Select...",
none: "None",
date: {
range: "{{start}} to {{end}}",
},
experience: "years of experience",
noExperience: "No experience",
firstName: "First name",
lastName: "Last name",
gender: "Gender",
male: "Male",
female: "Female",
dateOfBirth: "Date of birth",
phoneNumber: "Phone number",
socialNetwork: "The social network",
address: "address",
specialist: "Specialist",
title: "Title",
},
login: {
welcome: "Welcome!",
@@ -61,6 +78,15 @@ export default {
useDifferentEmail: "Use different email address or username",
signInWithGoogle: "Continue with google",
invalidEmail: "Invalid email format",
specialCharacterRequired:
"Paswword must contain at least one special character",
numberRequired: "Paswword must contain at least one number",
lowercaseRequired: "Paswword must contain at least one lowercase character",
uppercaseRequired: "Paswword must contain at least one uppercase character",
numberOfCharactersRequired: "Password must contain at least 8 characters",
retypePassword: "Please retype your password.",
matchPasswords: "Your passwords do not match.",
passwordConfirmation: "Password confirmation",
},
password: {
weak: "weak",
@@ -94,6 +120,7 @@ export default {
UsernameDoesNotExist: "Username does not exist",
},
nav: {
navigation: "Navigation",
ads: "Ads",
selectionFlow: "Selection flow",
candidates: "Candidates",
@@ -102,6 +129,7 @@ export default {
statistics: "Statistics",
users: "Users",
signOut: "Sign Out",
schedule:"Schedule"
},
ads: {
activeAds: "Active Ads",
@@ -114,6 +142,28 @@ export default {
adDetailsRequirements: "Requirements",
adDetailsOffer: "What We Offer",
archiveAdsCandidates: "Registered candidates",
adding: "Adding",
ad: "Ad",
ads: "Ads",
signUp: "Sign up",
dragPdf1: "Drag the .pdf document in this part of the screen or",
dragPdf2: "on the computer",
coverLetter: "Cover letter (Optional)",
professionalQualification: "Professional qualification",
technologies: "Technologies you know",
others: "Others",
noExperience2: "No experience needed",
registered: "registered",
backToAds: "Back to all ads",
thereIsNoAds: "Unfortunately, there are currently no active ads",
addAd: "You can always add a new one in just a few simple steps",
adNewAd: "Add an ad",
expirationDate: "Ad Expiration Date",
duties: "Main duties",
conditions: "Conditions",
offer: "What we offer",
archivingAd:"Archiving ad",
archivingAdQuestion:"Are you sure you want to archive the ad?",
},
users: {
management: "User management",
@@ -127,6 +177,19 @@ export default {
contact: "Contact",
phone: "Phone",
socials: "Social Media",
resetPassword:"Reset password",
resetLink:"Are you sure you want to send password reset link?",
disableUser:"Disable user",
questionDisableUser:"Are you sure you want to disable user?",
deleteUser:"Delete user",
questionDeleteUser:"Are you sure you want to delete user?",
block:"Block",
unblock:"Unblock",
profile:"Edit profile",
positionNotDeclared:"Position has not been declared yet.",
backToUsers: "Back to list of users",
noSocialMedia:"User takes not part in any social media.",
noPhoneNumber:"User has no phone number saved."
},
selectionLevels: {
done: {
@@ -136,9 +199,90 @@ export default {
FD: "Candidates accepted",
},
},
registration:{
phone: 'Phone',
linkedinProfile: 'LinkedIn profile',
position: 'Position',
registration: {
phone: "Phone",
linkedinProfile: "LinkedIn profile",
position: "Position",
link: "Registration link",
register: "Register",
twoSteps:"Two steps to the HR Center.",
mistake:"There was a mistake..."
},
filters: {
filters: "Filters",
experience: "Years of experience",
technologies: "Technologies",
employmentType: "Type of employment",
internship: "Internship",
work: "Work",
workHour: "Work hour",
partTime: "Part-time",
fullTime: "Full-time",
search: "Search",
dateOfApplication: "Date of application",
category: "Category",
creationDate: "Creation date",
advancedTechnologies: "Advanced technologies",
},
candidates: {
candidates: "Candidates",
candidate: "Candidate",
experience: "Experience",
informations: "Informations",
contact: "Contact",
comment: "Comment",
allApplications: "All applications",
backToCandidates: "Back to all candidates",
tableView: "Table view",
search: "Search",
position: "Position",
deleteCandidate:"Deleting candidates",
deleteCandidateQuestion:"Are you sure you want to delete the candidate?"
},
dialogs: {
candidateName: "Candidate's name",
interviewerName: "Interviewer's name (optional)",
interviewerName2: "Interviewer's name",
duration: "Duration",
appointment: "Appointment",
},
schedule: {
items: "items",
planner: "Activity planner",
},
selection: {
report: "Detailed report",
link: "Link to Google Meet",
noCandidates: "There are no candidates in the selection",
interviewer: "Interviewer",
noInterviewer:"The process has no interviewer.",
title:"Selection flow",
subtitle:"All candidates",
addInterviewer:"Adding an interviewer",
selection:"Selection",
changeInterviewer:"Change of interviewer",
addCandidate:"Adding candidate",
interview:"HR interview",
filterDate:"Date"
},
patterns:{
made:"Made",
pattern:"Pattern",
scheduling:"Scheduling an appointment",
messageText:"Message text",
message:"MESSAGE",
editing:"Editing",
patternsMade:"Patterns made",
noPatterns:"There are currently no patterns added",
addPattern:"Add pattern",
editing2:"Editin mode"
},
stats:{
statistic:"Statistic",
selectionFlow:"Selection flow",
relations:"Relations",
number:"Number",
contacted:"Number of contacted:",
registered:"Registered by positions"
}
};

+ 195
- 47
src/i18n/resources/rs.js 파일 보기

@@ -10,36 +10,53 @@ export default {
common: {
// close: 'Close',
// trademark: 'TM',
// search: 'Search',
search: "Pretrazi",
// error: 'Error',
// continue: 'Continue',
labelUsername: "Korisničko ime",
labelPassword: "Šifra",
labelConfirmPassword: "Ponovljena šifra",
or: "ili",
and: "i",
// next: 'Next',
// nextPage: 'Next page',
// previousPage: 'Previous page',
// back: 'Back',
back: "NAZAD",
// goBack: 'Go Back',
// ok: 'Ok',
// done: 'Done',
// confirm: 'Confirm',
confirm: "Potvrdi",
// printDownload: 'Print/Download',
// cancel: 'Cancel',
cancel: "Otkazi",
delete: "Obrisi",
change: "Promeni",
// remove: 'Remove',
// invite: 'Invite',
// save: 'Save',
// complete: 'Complete',
// download: 'Download',
download: "Preuzmi",
// yes: 'Yes',
// no: 'No',
// to: 'to',
to: "Do",
from: "Od",
// select: 'Select...',
// none: 'None',
// date: {
// range: '{{start}} to {{end}}',
// },
continue: "NASTAVI",
firstName: "Ime",
lastName: "Prezime",
gender: "Pol",
male: "Muski",
female: "Zenski",
dateOfBirth: "Datum rodjenja",
phoneNumber: "Broj telefona",
socialNetwork: "Drustvene mreze",
address: "adresa",
specialist: "Specijalista",
title: "Naslov",
experience: "godina iskustva",
},
login: {
welcome: "Dobrodošli!",
@@ -58,17 +75,17 @@ export default {
emailRequired: "Potrebno je uneti email adresu.",
// signUp: 'Sign Up',
usernameRequired: "Potrebno je uneti korisničko ime.",
passwordRequired: "Potrebno je uneti šifru.",
forgotYourPassword: "Zaboravio/la si šifru?",
resetYourPassword: "Nova šifra",
resetYourPasswordHelpText: "Unesi novu šifru.",
passwordRequired: "Potrebno je uneti lozinku.",
forgotYourPassword: "Zaboravio/la si lozinku?",
resetYourPassword: "Nova lozinka",
resetYourPasswordHelpText: "Unesi novu lozinku.",
forgotYourPasswordHelpText:
"Samo unesi e-mail adresu svog HR Center profila.",
forgotYourPasswordButton: "POŠALJI",
forgotYourPasswordBackLink: "Nazad na Login",
forgotYourPasswordConfimation:
"Proveri email adresu da bi resetovao šifru.",
passwordDontMatch: "Šifre se ne poklapaju.",
"Proveri email adresu da bi resetovao lozinku.",
passwordDontMatch: "Lozinke se ne poklapaju.",
// _useDifferentEmail: 'Use different email address or username',
// get useDifferentEmail() {
// return this._useDifferentEmail;
@@ -77,7 +94,16 @@ export default {
// this._useDifferentEmail = value;
// },
signInWithGoogle: "Prijava putem Google-a",
invalidEmail:"Format adrese nije validan"
invalidEmail: "Format adrese nije validan",
specialCharacterRequired:
"Lozinka mora da sadrzi najmanje jedan poseban znak",
numberRequired: "Lozinka mora da sadrzi najmanje jedan broj",
lowercaseRequired: "Lozinka mora da sadrzi najmanje jedno malo slovo",
uppercaseRequired: "Lozinka mora da sadrzi najmanje jedno veliko slovo",
numberOfCharactersRequired: "Lozinka mora da sadrzi najmanje 8 karaktera",
retypePassword: "Molimo ponovo unesite lozinku.",
matchPasswords: "Lozinke se ne poklapaju",
passwordConfirmation: "Potvrda lozinke",
},
// password: {
// weak: 'weak',
@@ -113,14 +139,15 @@ export default {
// },

nav: {
ads: 'Oglasi',
selectionFlow: 'Tok Selekcije',
candidates: 'Kandidati',
schedule: 'Planer',
patterns: 'Šabloni',
navigation: "Navigacija",
ads: "Oglasi",
selectionFlow: "Tok Selekcije",
candidates: "Kandidati",
schedule: "Planer",
patterns: "Šabloni",
statistics: "Statistika",
users: 'Korisnici',
signOut: 'Izloguj se'
users: "Korisnici",
signOut: "Izloguj se",
},
ads: {
activeAds: "Aktivni Oglasi",
@@ -133,33 +160,73 @@ export default {
adDetailsRequirements: "Zahtevi",
adDetailsOffer: "Šta Nudimo",
archiveAdsCandidates: "Prijavljeni kandidati",
experience: "godina iskustva",
noExperience: "Nema iskustva",
adding: "Dodavanje",
ad: "Oglas",
ads: "Oglasi",
signUp: "Prijavi se",
dragPdf1: "Prevuci .pdf dokument u ovom delu ekrana ili",
dragPdf2: "na racunaru",
coverLetter: "Propratno pismo (Opciono)",
professionalQualification: "Strucna sprema",
technologies: "Tehnologije koje znas",
others: "Druge",
noExperience2: "Nije potrebno iskustvo",
registered: "prijavljenih",
backToAds: "Nazad na sve oglase",
thereIsNoAds: "Nažalost, trenutno nema aktivnih oglasa",
addAd: "Uvek možete dodati novi u samo par jednostavnih koraka",
adNewAd: "Dodaj oglas",
expirationDate: "Datum isteka oglasa",
duties: "Glavna zaduženja",
conditions: "Uslovi",
offer: "Šta nudimo",
archivingAds:"Arhiviranje oglasa",
archivingAdQuestion:"Da li ste sigurni da želite da arhivirate oglas?",
},
selection: {
title: "Tok Selekcije",
subtitle: "Svi kandidati",
tipHeader: "Savet za uspešan Screening test",
tipBody: "Zapamtite da odeljenje za ljudske resurse u sebi sadrži reč „ljudski“. HR treba da vas vidi kakvi ste i da bi stekli osećaj za vašu stvarnu ličnost i postarali se da se dobro uklopite u kompaniju. Zato je bolje da se ponašate prirodno i opušteno. Važno je i pokazati da posedujete snažne međuljudske veštine i da se ponašate profesionalno. Na dan intervjua budite tačni, predstavite se pristojno i obucite se na odgovarajući način. To znači da razmislite o slici kompanije, ali i da se odevate na način koji vam je ugodan tokom dana.",
filterStatus: "Status",
filterDate: "Datum",
filterFrom: "Od",
filterTo: "Do",
filterSubmit: "Pretraži"
},
// selection: {
// title: "Tok Selekcije",
// subtitle: "Svi kandidati",
// tipHeader: "Savet za uspešan Screening test",
// tipBody:
// "Zapamtite da odeljenje za ljudske resurse u sebi sadrži reč „ljudski“. HR treba da vas vidi kakvi ste i da bi stekli osećaj za vašu stvarnu ličnost i postarali se da se dobro uklopite u kompaniju. Zato je bolje da se ponašate prirodno i opušteno. Važno je i pokazati da posedujete snažne međuljudske veštine i da se ponašate profesionalno. Na dan intervjua budite tačni, predstavite se pristojno i obucite se na odgovarajući način. To znači da razmislite o slici kompanije, ali i da se odevate na način koji vam je ugodan tokom dana.",
// filterStatus: "Status",
// filterDate: "Datum",
// filterFrom: "Od",
// filterTo: "Do",
// filterSubmit: "Pretraži",
// },
users: {
management: "Upravljanje korisnicima",
fullName: 'Ime i prezime',
position: 'Pozicija',
invite: 'Pozovi',
inviteUser: 'Pozovi korisnika',
regLink: 'Registracioni link',
receiver: 'Primalac',
user: 'Korisnik',
contact: 'Kontakt',
phone: 'Telefon',
socials: 'Društvene mreže',
fullName: "Ime i prezime",
position: "Pozicija",
invite: "Pozovi",
inviteUser: "Pozovi korisnika",
regLink: "Registracioni link",
receiver: "Primalac",
user: "Korisnik",
contact: "Kontakt",
phone: "Telefon",
socials: "Društvene mreže",
resetPassword: "Resetuj lozinku",
resetLink:
"Da li ste sigurni da zelite da posaljete link za resetovanje lozinke?",
disableUser: "Onemoguci korisnika",
questionDisableUser:
"Da li ste sigurni da zelite da onemogucite korisnika?",
deleteUser: "Obrisite korisnika",
questionDeleteUser: "Da li ste sigurni da zelite ukloniti korisnika?",
block: "Blokiraj",
unblock: "Odblokiraj",
profile: "Uredi profil",
positionNotDeclared: "Pozicija nije jos uvek odredjena.",
backToUsers: "Nazad na listu korisnika",
noSocialMedia: "Korisnik nije deo nijedne drustvene mreze.",
noPhoneNumber: "Korisnik nema sacuvan broj telefona.",
},
filter: {
title: "Filteri"
title: "Filteri",
},
selectionLevels: {
done: {
@@ -169,9 +236,90 @@ export default {
FD: "Primljenih kandidata",
},
},
registration:{
phone: 'Broj Telefona',
linkedinProfile: 'LinkedIn profil',
position: 'Pozicija',
}
registration: {
phone: "Broj Telefona",
linkedinProfile: "LinkedIn profil",
position: "Pozicija",
link: "Registracioni link",
register: "Registruj se",
twoSteps: "Dva koraka do HR Centra.",
mistake: "Doslo je do greske...",
},
filters: {
filters: "Filteri",
experience: "Godine iskustva",
technologies: "Tehnologije",
employmentType: "Tip zaposlenja",
internship: "Strucna praksa",
work: "Posao",
workHour: "Radno vreme",
partTime: "Skraceno vreme",
fullTime: "Puno vreme",
search: "Pretrazi",
dateOfApplication: "Datum prijave",
category: "Kategorija",
creationDate: "Datum kreiranja",
advancedTechnologies: "Napredne tehnologije",
},
candidates: {
candidates: "Kandidati",
candidate: "Kandidat",
experience: "Iskustvo",
informations: "Informacije",
contact: "Kontakt",
comment: "Komentar",
allApplications: "Sve prijave",
backToCandidates: "Nazad na sve kandidate",
tableView: "Tablicni prikaz",
search: "Pretraga",
position: "Pozicija",
deleteCandidate:"Brisanje kandidata",
deleteCandidateQuestion:"Da li ste sigurni da zelite da obrisete kandidata?"
},
dialogs: {
candidateName: "Ime kandidata",
interviewerName: "Ime intervjuera (opciono)",
interviewerName2: "Ime intervjuera",
duration: "Trajanje",
appointment: "Termin",
},
schedule: {
items: "stavke",
planner: "Planer aktivnosti",
},
selection: {
report: "Detaljni izvestaj",
link: "Link do Google Meet-a",
noCandidates: "Nema kandidata u selekciji",
interviewer: "Intervjuer",
noInterviewer: "Proces nema intervjuera.",
title: "Tok selekcije",
subtitle: "Svi kandidati",
addInterviewer:"Dodavanje intervjuera",
selection:"Selekcija",
changeInterviewer:"Promena intervjuera",
addCandidate:"Dodavanje kandidata",
interview:"HR intervju",
filterDate:"Datum"
},
patterns: {
made: "Napravljen",
pattern: "Šablon",
scheduling: "Zakazivanje termina",
messageText: "Tekst poruke",
message: "PORUKU",
editing: "Uređivanje",
patternsMade: "Napravljeni Šabloni",
noPatterns: "Trenutno nema dodatih sablona",
addPattern: "Dodaj Šablon",
editing2:"Režim uređivanja"
},
stats: {
statistic: "Statistika",
selectionFlow: "Tok selekcije",
relations: "Odnosi",
number: "Broj",
contacted: "Broj kontaktiranih:",
registered: "Prijavljeni po pozicijama",
},
};

+ 9
- 7
src/pages/AdsPage/AdDetailsPage.js 파일 보기

@@ -133,7 +133,9 @@ const AdDetailsPage = () => {
<h1>{ad.title}</h1>
</div>
<div className="ad-details-tech-logo-title-sub">
<sub>| {ad.totalApplicants} prijavljenih</sub>
<sub>
| {ad.totalApplicants} {t("ads.registered")}
</sub>
</div>
</div>
{!(new Date(ad.expiredAt) < new Date()) && (
@@ -156,10 +158,10 @@ const AdDetailsPage = () => {
</div>
<ConfirmDialog
open={showArchiveAdDialog}
title={"Arhiviranje oglasa"}
title={t("ads.archivingAd")}
subtitle={ad.title}
imgSrc={archiveIcon}
content="Da li ste sigurni da želite da arhivirate oglas?"
content={t("ads.archivingAdQuestion")}
onClose={() => {
setShowArchiveAdDialog(false);
}}
@@ -172,8 +174,8 @@ const AdDetailsPage = () => {
</p>
</div>
<div className="ad-details-content-work-time">
<button>Posao</button>
<button>Full-time</button>
<button>{t("filters.work")}</button>
<button>{t("filters.fullTime")}</button>
</div>
<div className="ad-details-content-content">
<div className="ad-details-content-conten-description">
@@ -266,7 +268,7 @@ const AdDetailsPage = () => {
className="ad-details-buttons-link"
onClick={() => history.push({ pathname: ADS_PAGE })}
>
Nazad na sve oglase
{t("ads.backToAds")}
</button>

{!(new Date(ad.expiredAt) < new Date()) && (
@@ -274,7 +276,7 @@ const AdDetailsPage = () => {
className="c-btn c-btn--primary add-ad-btn apply-for-ad-button"
onClick={() => setApplyForAdOpenModal(true)}
>
PRIJAVI SE
{t("ads.signUp").toUpperCase()}
</IconButton>
)}
</div>

+ 5
- 5
src/pages/AdsPage/AdsPage.js 파일 보기

@@ -280,7 +280,7 @@ const AdsPage = ({ history }) => {
}
onClick={() => handleChangeVisibility(true)}
>
{!matches && "Pretraga"}
{!matches && t("common.search")}
<img
style={{
position: "relative",
@@ -356,14 +356,14 @@ const AdsPage = ({ history }) => {
<FilterButton onShowFilters={handleToggleFiltersDrawer} />
</div>
<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>
<h1>{t("ads.thereIsNoAds")}</h1>
<p>{t("ads.addAd")}</p>
<div className="add-ad add-ad-no-ads">
<IconButton
className="c-btn ads-page-btn c-btn--primary add-ad-btn"
onClick={() => history.push(CREATE_AD_PAGE)}
>
Dodaj Oglas
{t("ads.adNewAd")}
</IconButton>
</div>
</div>
@@ -441,7 +441,7 @@ const AdsPage = ({ history }) => {
className="c-btn ads-page-btn c-btn--primary add-ad-btn"
onClick={createAd}
>
+ Oglas
+ {t("ads.ad")}
</IconButton>
</div>
)}

+ 10
- 9
src/pages/AdsPage/CreateAdFirstStep.js 파일 보기

@@ -1,5 +1,6 @@
import React from "react";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";

const CreateAdFirstStep = ({
title,
@@ -11,7 +12,7 @@ const CreateAdFirstStep = ({
expiredAt,
setExpiredAt,
}) => {
const { t } = useTranslation();
const employmentTypeHandler = (type) => {
setEmploymentType(type);
};
@@ -23,7 +24,7 @@ const CreateAdFirstStep = ({
return (
<div data-testid="create-ad-first-step-form">
<div className="create-ad-form-control">
<label>Naslov</label>
<label>{t("common.title")}</label>
<input
type="text"
className="create-ad-form-control-first-step-input"
@@ -33,7 +34,7 @@ const CreateAdFirstStep = ({
/>
</div>
<div className="create-ad-form-control">
<label>Tip zaposlenja</label>
<label>{t("filters.employmentType")}</label>
<div className="create-ad-form-control-buttons">
<button
className={`c-btn ${
@@ -43,7 +44,7 @@ const CreateAdFirstStep = ({
}`}
onClick={employmentTypeHandler.bind(this, "Work")}
>
Posao
{t("filters.work")}
</button>
<button
className={`c-btn ${
@@ -53,12 +54,12 @@ const CreateAdFirstStep = ({
}`}
onClick={employmentTypeHandler.bind(this, "Intership")}
>
Intership
{t("filters.internship")}
</button>
</div>
</div>
<div className="create-ad-form-control">
<label>Radno vreme</label>
<label>{t("filters.workHour")}</label>
<div className="create-ad-form-control-buttons">
<button
className={`c-btn ${
@@ -68,7 +69,7 @@ const CreateAdFirstStep = ({
}`}
onClick={workHourHandler.bind(this, "PartTime")}
>
Part-time
{t("filters.partTime")}
</button>
<button
className={`c-btn ${
@@ -78,12 +79,12 @@ const CreateAdFirstStep = ({
}`}
onClick={workHourHandler.bind(this, "FullTime")}
>
Full-time
{t("filters.fullTime")}
</button>
</div>
</div>
<div className="create-ad-form-control">
<label>Datum isteka oglasa</label>
<label>{t("ads.expirationDate")}</label>
<input
type="date"
className="create-ad-form-control-first-step-input"

+ 7
- 5
src/pages/AdsPage/CreateAdPage.js 파일 보기

@@ -13,6 +13,7 @@ import CreateAdSecondStep from "./CreateAdSecondStep";
import CreateAdThirdStep from "./CreateAdThirdStep";
import { useHistory } from "react-router-dom";
import { ADS_PAGE } from "../../constants/pages";
import { useTranslation } from "react-i18next";

const CreateAdPage = () => {
const [stage, setStage] = useState(1);
@@ -23,6 +24,7 @@ const CreateAdPage = () => {
const [experience, setExperience] = useState(0);
const childRef = useRef();
const history = useHistory();
const { t } = useTranslation();

const technologies = useSelector(
(state) => state.addAdTechnologies.technologies
@@ -115,9 +117,9 @@ const CreateAdPage = () => {
alt="plus"
style={{ width: "18px", height: "18px" }}
/>
<h2>Dodavanje</h2>
<h2>{t("ads.adding")}</h2>
<h2>
<sub> | Oglas</sub>
<sub> | {t("ads.ad")}</sub>
</h2>
</div>
<div className="create-ad-steps">
@@ -148,19 +150,19 @@ const CreateAdPage = () => {
disabled={stage === 1}
onClick={backClickHandler}
>
NAZAD
{t("common.back")}
</button>
<button
className="create-ad-buttons-forward"
onClick={forwardClickHandler}
>
NASTAVI
{t("common.continue")}
</button>
</div>
</div>
<div className="create-ad-go-back">
<button onClick={() => history.push(ADS_PAGE)}>
Nazad na sve oglase
{t("ads.backToAds")}
</button>
</div>
</div>

+ 5
- 3
src/pages/AdsPage/CreateAdSecondStep.js 파일 보기

@@ -3,8 +3,10 @@ import PropTypes from "prop-types";
import { Checkbox, FormControlLabel } from "@mui/material";
import { useDispatch } from "react-redux";
import { changeIsCheckedAddAdValue } from "../../store/actions/addAdTechnologies/addAdTechnologiesActions";
import { useTranslation } from "react-i18next";

const CreateAdSecondStep = ({ technologies, experience, setExperience }) => {
const { t } = useTranslation;
const dispatch = useDispatch();

const handleCheckboxes = (technologyId) => {
@@ -14,7 +16,7 @@ const CreateAdSecondStep = ({ technologies, experience, setExperience }) => {
return (
<div data-testid="create-ad-second-step-form">
<div className="create-ad-form-control">
<label>Napredne tehnologije</label>
<label>{t("filters.advancedTechnologies")}</label>
</div>
<div className="add-ad-modal-stage-sub-card">
<div className="add-ad-modal-stage-sub-card-checkboxes-group">
@@ -62,7 +64,7 @@ const CreateAdSecondStep = ({ technologies, experience, setExperience }) => {
</div>

<div className="add-ad-modal-stage-sub-card-checkboxes-group">
<label>Other</label>
<label>{t("ads.others")}</label>
<div className="add-ad-modal-stage-sub-card-checkboxes">
{technologies
.filter((x) => x.technologyType === "Other")
@@ -84,7 +86,7 @@ const CreateAdSecondStep = ({ technologies, experience, setExperience }) => {
</div>
</div>
<div className="create-ad-form-control">
<label>Godine iskustva</label>
<label>{t("filters.experience")}</label>
<input
type="number"
min={0}

+ 5
- 3
src/pages/AdsPage/CreateAdThirdStep.js 파일 보기

@@ -3,8 +3,10 @@ import PropTypes from "prop-types";
import { Editor } from "@tinymce/tinymce-react";
import { useRef } from "react";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";

const CreateAdThirdStep = ({ childRef }) => {
const { t } = useTranslation();
const editorKeyResponsibilitiesRef = useRef();
const editorRequirementsRef = useRef();
const editorOfferRef = useRef();
@@ -24,7 +26,7 @@ const CreateAdThirdStep = ({ childRef }) => {
return (
<div data-testid="create-ad-third-step-form">
<div className="create-ad-form-control">
<label>Glavna zaduženja</label>
<label>{t("ads.duties")}</label>
<Editor
onInit={(evt, editor) =>
(editorKeyResponsibilitiesRef.current = editor)
@@ -33,14 +35,14 @@ const CreateAdThirdStep = ({ childRef }) => {
/>
</div>
<div className="create-ad-form-control">
<label>Uslovi</label>
<label>{t("ads.conditions")}</label>
<Editor
onInit={(evt, editor) => (editorRequirementsRef.current = editor)}
style={{ height: "1rem !important" }}
/>
</div>
<div className="create-ad-form-control">
<label>Šta nudimo</label>
<label>{t("ads.offer")}</label>
<Editor
onInit={(evt, editor) => (editorOfferRef.current = editor)}
style={{ height: "1rem !important" }}

+ 3
- 1
src/pages/CandidatesPage/AdsCandidatesPage.js 파일 보기

@@ -11,6 +11,7 @@ import PropTypes from "prop-types";
import { useTheme } from "@mui/system";
import { useMediaQuery } from "@mui/material";
import { selectAdsCandidates } from "../../store/selectors/candidatesSelectors";
import { useTranslation } from "react-i18next";

const AdsCandidatesPage = ({ history, search }) => {
const dispatch = useDispatch();
@@ -18,6 +19,7 @@ const AdsCandidatesPage = ({ history, search }) => {
const [getRef, setRef] = useDynamicRefs();
const theme = useTheme();
const matches = useMediaQuery(theme.breakpoints.down("361"));
const {t} = useTranslation()

useEffect(() => {
dispatch(
@@ -108,7 +110,7 @@ const AdsCandidatesPage = ({ history, search }) => {
<img src={adImage} className="ads-candidates-image" />
<p className="ads-candidates-title">{adCandidates.title}</p>
<p className="ads-candidates-numberOfApplicants">
| {adCandidates.nubmerOfApplicants} prijavljenih
| {adCandidates.nubmerOfApplicants} {t("ads.registered")}
</p>
</div>
<div className="ads-candidates-slider">

+ 40
- 20
src/pages/CandidatesPage/CandidateDetailsPage.js 파일 보기

@@ -24,6 +24,7 @@ import deleteIcon from "../../assets/images/delete.png";
import { selectCandidate } from "../../store/selectors/candidateSelectors";
import { selectAuthUser } from "../../store/selectors/userSelectors";
import { selectUsers } from "../../store/selectors/usersSelector";
import { useTranslation } from "react-i18next";

const CandidateDetailsPage = ({ history }) => {
const dispatch = useDispatch();
@@ -38,6 +39,7 @@ const CandidateDetailsPage = ({ history }) => {
const adsSliderRef = useRef();
const [showDelete, setDelete] = useState(false);
const [usersToNotify, setUsersToNotify] = useState([]); //emails of users which are taged in comment
const { t } = useTranslation();

useEffect(() => {
dispatch(fetchCandidate({ id }));
@@ -217,10 +219,10 @@ const CandidateDetailsPage = ({ history }) => {
<div className="main-candidate-container">
<ConfirmDialog
open={showDelete}
title={"Brisanje kandidata"}
title={t("candidates.deleteCandidate")}
subtitle={candidate.firstName + " " + candidate.lastName}
imgSrc={deleteIcon}
content="Da li ste sigurni da zelite da obrisete kandidata?"
content={t("candidates.deleteCandidateQuestion")}
onClose={() => {
setDelete(false);
}}
@@ -230,7 +232,7 @@ const CandidateDetailsPage = ({ history }) => {
<div className="r-b-rectangle"></div>
<div className="top-candidate-container">
<div>
<p className="candidate-header">Kandidat</p>
<p className="candidate-header">{t("candidates.candidate")}</p>
<span className="separation-line">|</span>
<p className="candidate-lower-header">
{candidate.firstName} {candidate.lastName}
@@ -241,7 +243,7 @@ const CandidateDetailsPage = ({ history }) => {
className="c-btn c-btn--primary-outlined candidate-btn"
onClick={() => setDelete(true)}
>
Obrisi
{t("common.delete")}
<img src={deleteImage} alt="delete" className="candidates-image" />
</IconButton>
</div>
@@ -250,8 +252,8 @@ const CandidateDetailsPage = ({ history }) => {
<div className="details-candidate-container">
<p style={{ margin: 0 }} data-testid="candidate-experience">
{candidate.experience === 0
? "No experience"
: "Experience:" + candidate.experience}
? t("common.noExperience")
: t("candidates.experience") + ":" + candidate.experience}
</p>
<div className="technologies-candidate-container">
{candidate.technologyApplicants.map((obj, index) => (
@@ -263,16 +265,25 @@ const CandidateDetailsPage = ({ history }) => {
<div className="candidate-informations-container">
<div className="candidate-informations-sub-container">
<div className="candidate-property-container">
<p className="informations-candidate-header">Informacije</p>
<p className="candidate-property">Pol:</p>
<p className="candidate-property">Strucna sprema:</p>
<p className="informations-candidate-header">
{t("candidates.informations")}
</p>
<p className="candidate-property">{t("common.gender")}:</p>
<p className="candidate-property">
{t("ads.professionalQualification")}:
</p>
</div>
<div
style={{ alignSelf: "flex-end", marginLeft: 42 }}
className="candidate-property-container"
>
<p className="candidate-property-value" data-testId="candidate-gender">
{candidate.gender === "M" ? "Muski" : "Zenski"}
<p
className="candidate-property-value"
data-testId="candidate-gender"
>
{candidate.gender === "M"
? t("common.male")
: t("common.female")}
</p>
<p className="candidate-property-value">
{candidate.professionalQualification === ""
@@ -283,9 +294,11 @@ const CandidateDetailsPage = ({ history }) => {
</div>
<div className="candidate-informations-sub-container">
<div className="candidate-property-container">
<p className="informations-candidate-header">Kontakt</p>
<p className="informations-candidate-header">
{t("candidates.contact")}
</p>
<p className="candidate-property">Email:</p>
<p className="candidate-property">Telefon:</p>
<p className="candidate-property">{t("users.phone")}:</p>
</div>
<div
style={{ alignSelf: "flex-end", marginLeft: 42 }}
@@ -299,7 +312,9 @@ const CandidateDetailsPage = ({ history }) => {
</div>
<div className="candidate-informations-sub-container">
<div className="candidate-property-container">
<p className="informations-candidate-header">Drustvene mreze</p>
<p className="informations-candidate-header">
{t("users.socials")}
</p>
<p className="candidate-property">Linkedln</p>
<p className="candidate-property">GitHub</p>
<p className="candidate-property">BitBucket</p>
@@ -357,7 +372,7 @@ const CandidateDetailsPage = ({ history }) => {
</div>
<div className="comment-separation-line"></div>
<div className="send-comment-container">
<p>Komentar</p>
<p>{t("candidates.comment")}</p>
<div className="send-comment-sub-container">
<MentionsInput
value={inputValue}
@@ -411,7 +426,7 @@ const CandidateDetailsPage = ({ history }) => {
alt="plane"
className="candidates-image"
/>
<p>Komentar</p>
<p>{t("candidates.comment")}</p>
</div>
) : (
<div
@@ -430,7 +445,9 @@ const CandidateDetailsPage = ({ history }) => {
</div>
</div>
<div className="applicant-ads-container">
<p style={{ marginLeft: matches ? 36 : 144 }}>Sve prijave</p>
<p style={{ marginLeft: matches ? 36 : 144 }}>
{t("candidates.allApplications")}
</p>
<div className="applicant-ads-container-2">
<div
style={{
@@ -440,7 +457,10 @@ const CandidateDetailsPage = ({ history }) => {
{(matches
? candidate.ads.length > 1
: candidate.ads.length > 5) && (
<div className="active-ads-ads-arrows" data-testid="candidate-ad-responsive-arrows">
<div
className="active-ads-ads-arrows"
data-testid="candidate-ad-responsive-arrows"
>
<button onClick={activeAdsArrowLeftHandler}>
<img src={arrow_left} alt="arrow-left" />
</button>
@@ -468,7 +488,7 @@ const CandidateDetailsPage = ({ history }) => {
className="applicant-ads-back-button"
onClick={goToPageWithAllCandidates}
>
Nazad na sve kandidate
{t("candidates.backToCandidates")}
</p>
<a
className="applicant-cv-button"
@@ -476,7 +496,7 @@ const CandidateDetailsPage = ({ history }) => {
href={`data:application/pdf;base64,${candidate.cv}`}
title="Download pdf document"
>
Preuzmi cv
{t("common.download")} cv
</a>
</div>
</div>

+ 8
- 6
src/pages/CandidatesPage/CandidatesPage.js 파일 보기

@@ -14,6 +14,7 @@ import { setTechnologiesReq } from "../../store/actions/technologies/technologie
import { selectTechnologies } from "../../store/selectors/technologiesSelectors";
import { useDispatch, useSelector } from "react-redux";
import Fade from "@mui/material/Fade";
import { useTranslation } from "react-i18next";

const CandidatesPage = ({ history }) => {
const dispatch = useDispatch();
@@ -33,6 +34,7 @@ const CandidatesPage = ({ history }) => {
{ name: "Posao", isChecked: false },
{ name: "Intership", isChecked: false },
]);
const { t } = useTranslation();

useEffect(() => {
dispatch(setTechnologiesReq());
@@ -95,7 +97,7 @@ const CandidatesPage = ({ history }) => {
<div className="top-candidates-container">
{!matches ? (
<p className="candidates-header" data-testid="candidates-header1">
Kandidati
{t("candidates.candidates")}
</p>
) : (
<p
@@ -103,7 +105,7 @@ const CandidatesPage = ({ history }) => {
data-testid="candidates-header2"
style={{ fontSize: "22px" }}
>
Kandidati
{t("candidates.candidates")}
</p>
)}
<div style={{ postion: "relative" }}>
@@ -129,7 +131,7 @@ const CandidatesPage = ({ history }) => {
className="c-btn c-btn--primary-outlined candidate-btn all-white-btn"
onClick={changeView}
>
Tablicni prikaz
{t("candidates.tableView")}
<img
src={tableImage}
alt="table"
@@ -141,7 +143,7 @@ const CandidatesPage = ({ history }) => {
className="c-btn c-btn--primary-outlined candidate-btn candidate-btn-view-2"
onClick={changeView}
>
Tablicni prikaz
{t("candidates.tableView")}
<img
src={tableImage}
alt="table"
@@ -166,7 +168,7 @@ const CandidatesPage = ({ history }) => {
className="c-btn c-btn--primary-outlined candidate-btn"
onClick={handleChangeVisibility}
>
Pretraga
{t("candidates.search")}
<img
src={searchImage}
alt="filter"
@@ -179,7 +181,7 @@ const CandidatesPage = ({ history }) => {
className="c-btn c-btn--primary-outlined candidate-btn candidate-btn-filters1"
onClick={handleToggleFiltersDrawer}
>
Filteri
{t("filters.filters")}
<img
src={filterImage}
alt="filter"

+ 9
- 4
src/pages/CandidatesPage/TableViewPage.js 파일 보기

@@ -14,6 +14,8 @@ import {
} from "../../store/selectors/candidatesSelectors";
import { getCV } from "../../request/candidatesRequest";
import Fade from "@mui/material/Fade";
import { useTranslation } from "react-i18next";

const TableViewPage = ({
history,
setPage,
@@ -32,6 +34,7 @@ const TableViewPage = ({
const matches = useMediaQuery(theme.breakpoints.down("361"));
const [linkToCV, setLinkToCV] = useState("");
const [isCVDisplayed, setIsCVDisplayed] = useState(false);
const { t } = useTranslation();

useEffect(() => {
dispatch(
@@ -140,10 +143,12 @@ const TableViewPage = ({
>
<thead>
<tr className="headingRow">
<th>Ime i prezime</th>
{!isCVDisplayed && <th>Iskustvo</th>}
<th>Datum prijave</th>
<th>Pozicija</th>
<th>
{t("common.firstName")} {t("common.and")} {t("common.lastName")}
</th>
{!isCVDisplayed && <th>{t("candidates.experience")}</th>}
<th>{t("filters.dateOfApplication")}</th>
<th>{t("candidates.position")}</th>
<th>CV link</th>
</tr>
</thead>

+ 8
- 6
src/pages/PatternsPage/PatternDetailsPage.js 파일 보기

@@ -16,6 +16,7 @@ import { selectPatternApplicants } from "../../store/selectors/patternApplicants
import { PATTERNS_PAGE } from "../../constants/pages";
import { useHistory } from "react-router-dom";
import parse from "html-react-parser";
import { useTranslation } from "react-i18next";

const PatternDetailsPage = () => {
const [emails, setEmails] = useState([]);
@@ -29,6 +30,7 @@ const PatternDetailsPage = () => {
const { id } = useParams();
const dispatch = useDispatch();
const history = useHistory();
const { t } = useTranslation();

const navigateToPatternsPage = () => {
history.push(PATTERNS_PAGE);
@@ -102,17 +104,17 @@ const PatternDetailsPage = () => {
<div className="pattern-details" data-testid="pattern-details">
<div className="pattern-details-header">
<p>
<span>Napravljen:</span>{" "}
<span>{t("patterns.made")}:</span>{" "}
{new Date(pattern.createdAt).toLocaleDateString()}
</p>
</div>
<div className="pattern-details-card">
<div className="pattern-details-card-title">
<div className="pattern-details-card-title-title">
<h1>Šablon</h1>
<h1>{t("pattern.pattern")}</h1>
</div>
<div className="pattern-details-card-title-sub">
<sub> | Zakazivanje termina</sub>
<sub> | {t("patterns.scheduling")}</sub>
</div>
</div>
<div className="pattern-details-card-sub-card">
@@ -165,7 +167,7 @@ const PatternDetailsPage = () => {

<div className="pattern-details-card-sub-card">
<div className="pattern-details-card-sub-card-title">
<p>Teskt poruke</p>
<p>{t("patterns.messageText")}</p>
</div>
<div className="pattern-details-card-sub-card-message-pattern">
{/* <textarea disabled value={pattern.message}></textarea> */}
@@ -179,7 +181,7 @@ const PatternDetailsPage = () => {
data-testid="ad-details-buttons-link"
onClick={() => history.push(PATTERNS_PAGE)}
>
Nazad na sve oglase
{t("ads.backToAds")}
</button>
<IconButton
disabled={emails && emails.length === 0}
@@ -195,7 +197,7 @@ const PatternDetailsPage = () => {
}}
src={sendMessage}
/>
PORUKU
{t("patterns.message")}
</IconButton>
</div>
</div>

+ 16
- 14
src/pages/PatternsPage/PatternsPage.js 파일 보기

@@ -19,6 +19,7 @@ import { updatePatternReq } from "../../store/actions/updatePattern/updatePatter
import PatternFilters from "../../components/Patterns/PatternFilters";
import IconButton from "../../components/IconButton/IconButton";
import { Editor } from "@tinymce/tinymce-react";
import { useTranslation } from "react-i18next";

const PatternsPage = ({ history }) => {
const theme = useTheme();
@@ -36,6 +37,7 @@ const PatternsPage = ({ history }) => {
const dispatch = useDispatch();
const addPatternEditor = useRef();
const editPatternEditor = useRef();
const { t } = useTranslation();

useEffect(() => {
dispatch(setPatternsReq());
@@ -149,10 +151,10 @@ const PatternsPage = ({ history }) => {
<img src={plusIcon} alt="plus" />
</div>
<div className="add-pattern-modal-header-title-title">
<p>Dodavanje</p>
<p>{t("ads.adding")}</p>
</div>
<div className="add-pattern-modal-header-title-sub">
<sub> | Šablon</sub>
<sub> | {t("patterns.pattern")}</sub>
</div>
</div>
<div
@@ -164,7 +166,7 @@ const PatternsPage = ({ history }) => {
</div>
<form onSubmit={submitAddPatternHandler}>
<div className="add-pattern-modal-form-control">
<label>Naslov</label>
<label>{t("common.title")}</label>
<input
type="text"
placeholder="ex. Datum HR intervjua"
@@ -173,7 +175,7 @@ const PatternsPage = ({ history }) => {
/>
</div>
<div className="add-pattern-modal-form-control">
<label>Kategorija</label>
<label>{t("filters.category")}</label>
<select
name="add-pattern-category"
value={addPatternCategory}
@@ -188,7 +190,7 @@ const PatternsPage = ({ history }) => {
</select>
</div>
<div className="add-pattern-modal-form-control">
<label>Tekst poruke</label>
<label>{t("patterns.messageText")}</label>
<Editor
onInit={(evt, editor) => (addPatternEditor.current = editor)}
style={{ height: "100px !important" }}
@@ -210,10 +212,10 @@ const PatternsPage = ({ history }) => {
<img src={userPageBtnIcon} alt="plus" />
</div>
<div className="edit-pattern-modal-header-title-title">
<p>Uređivanje</p>
<p>{t("patterns.editing")}</p>
</div>
<div className="edit-pattern-modal-header-title-sub">
<sub> | Šablon</sub>
<sub> | {t("patterns.pattern")}</sub>
</div>
</div>
<div
@@ -225,7 +227,7 @@ const PatternsPage = ({ history }) => {
</div>
<form onSubmit={submitEditPatternHandler}>
<div className="edit-pattern-modal-form-control">
<label>Naslov</label>
<label>{t("common.title")}</label>
<input
type="text"
placeholder="ex. Datum HR intervjua"
@@ -239,7 +241,7 @@ const PatternsPage = ({ history }) => {
/>
</div>
<div className="edit-pattern-modal-form-control">
<label>Kategorija</label>
<label>{t("filters.category")}</label>
<select
name="edit-pattern-category"
value={editPattern ? editPattern.selectionLevelId : 1}
@@ -259,7 +261,7 @@ const PatternsPage = ({ history }) => {
</select>
</div>
<div className="edit-pattern-modal-form-control">
<label>Tekst poruke</label>
<label>{t("patterns.messageText")}</label>
<Editor
initialValue={editPattern ? editPattern.message : ""}
onInit={(evt, editor) => (editPatternEditor.current = editor)}
@@ -274,7 +276,7 @@ const PatternsPage = ({ history }) => {
<div className="patterns">
<div className="patterns-header">
<div>
<h1>Napravljeni Šabloni</h1>
<h1>{t("patterns.patternsMade")}</h1>
</div>
<div style={{ display: "flex" }}>
<IconButton
@@ -283,7 +285,7 @@ const PatternsPage = ({ history }) => {
isShownEdit && "pattern-header-active-button"
}`}
>
{!matches && "Režim uređivanja"}
{!matches && t("patterns.editing2")}
<img
style={{
position: "relative",
@@ -304,7 +306,7 @@ const PatternsPage = ({ history }) => {
)}
{patterns && patterns.length === 0 && (
<div>
<p>Trenutno nema dodatih sablona</p>
<p>{t("patterns.noPatterns")}</p>
</div>
)}
{patterns &&
@@ -331,7 +333,7 @@ const PatternsPage = ({ history }) => {
className="c-btn c-btn--primary add-ad-btn add-pattern-btn"
onClick={() => setOpenAddPatternModal(true)}
>
Dodaj Šablon
{t("patterns.addPattern")}
</IconButton>
</div>
</div>

+ 2
- 2
src/pages/RegisterPage/RegisterPage.js 파일 보기

@@ -119,7 +119,7 @@ const RegisterPage = ({ history }) => {
{t("login.welcome")}
</Typography>
<Typography variant="p" sx={{ mb: 2 }}>
Dva koraka do HR Centra.
{t("registration.twoSteps")}
</Typography>
<div className="steps-cont">
<div
@@ -145,7 +145,7 @@ const RegisterPage = ({ history }) => {
<Step />
</>
) : (
<h3 data-testid='error-h' style={{ margin: "50px 0px" }}>There was a mistake...</h3>
<h3 data-testid='error-h' style={{ margin: "50px 0px" }}>{t("registration.mistake")}</h3>
)}
</Box>
</Container>

+ 3
- 1
src/pages/SchedulePage/SchedulePage.js 파일 보기

@@ -12,6 +12,7 @@ import {
getFormatedDayOrMonth,
} from "../../util/helpers/dateHelpers";
import PropTypes from "prop-types";
import { useTranslation } from "react-i18next";

const SchedulePage = ({ history }) => {
const dispatch = useDispatch();
@@ -24,6 +25,7 @@ const SchedulePage = ({ history }) => {
);
const [currentlySelectedDate, setCurrentlySelectedDate] = useState("");
const [currentlySelectedDay, setCurrentlySelectedDay] = useState(0);
const { t } = useTranslation();

useEffect(() => {
dispatch(fetchSchedule({ month: currentMonth + 1, year: currentYear }));
@@ -130,7 +132,7 @@ const SchedulePage = ({ history }) => {
history={history}
/>
<div>
<p className="schedule-page-main-header">Planer aktivnosti</p>
<p className="schedule-page-main-header">{t("schedule.planner")}</p>
<p className="schedule-page-header">
| {getMonthString(currentMonth)} {currentYear}
</p>

+ 4
- 4
src/pages/SelectionProcessPage/SelectionProcessOfApplicantPage.js 파일 보기

@@ -105,7 +105,7 @@ const SelectionProcessOfApplicantPage = () => {
<div className="l-t-rectangle"></div>
<div className="r-b-rectangle"></div>
{/* <AdFilters /> */}
<div data-testid='appl-sel' className="ads">
<div data-testid="appl-sel" className="ads">
{processes && processes.length > 0 && (
<div className="active-ads">
<div className="active-ads-header">
@@ -147,7 +147,7 @@ const SelectionProcessOfApplicantPage = () => {
schedguler={
process?.scheduler
? `${process?.scheduler?.firstName} ${process?.scheduler?.lastName}`
: "Proces nema intervjuera."
: t("selection.noInterviewer")
}
link={process.link}
date={new Date(process.date)}
@@ -181,7 +181,7 @@ const SelectionProcessOfApplicantPage = () => {
{applicant?.selectionProcesses?.some(
(n) => n.status === "Neuspešno"
)
? "Komentar:"
? t("candidates.comment") + ":"
: t("selection.tipHeader")}
</h3>
<p>
@@ -201,7 +201,7 @@ const SelectionProcessOfApplicantPage = () => {
</div>
<div className="add-ad">
<Link className="ad-details-buttons-link" to="/selectionFlow">
Nazad na sve kandidate
{t("candidates.backToCandidates")}
</Link>
</div>
</>

+ 8
- 15
src/pages/SelectionProcessPage/SelectionProcessPage.js 파일 보기

@@ -3,7 +3,6 @@ import { useSelector } from "react-redux";
import Selection from "../../components/Selection/Selection";
import FilterButton from "../../components/Button/FilterButton";
import { useTranslation } from "react-i18next";
import AddAdModal from "../../components/Ads/AddAdModal";
import { useDispatch } from "react-redux";
import { setDoneProcess } from "../../store/actions/processes/processAction";
import { setProcessesReq } from "../../store/actions/processes/processesAction";
@@ -27,7 +26,6 @@ import CommentProcessDialog from "../../components/MUI/CommentProcessDialog";

const SelectionProcessPage = ({ history }) => {
const [toggleFiltersDrawer, setToggleFiltersDrawer] = useState(false);
const [toggleModal, setToggleModal] = useState(false);
const [toggleInitModal, setToggleInitModal] = useState(false);
// const errorMessage = useSelector(selectProcessesError);
const process = useSelector(selectDoneProcess);
@@ -73,10 +71,6 @@ const SelectionProcessPage = ({ history }) => {
setToggleFiltersDrawer((oldState) => !oldState);
};

const handleToggleModal = () => {
setToggleModal((oldState) => !oldState);
};

const renderList = processes?.map((item, index) => {
return (
<Selection
@@ -104,8 +98,8 @@ const SelectionProcessPage = ({ history }) => {
/>
<StatusDialog
open={activeProcess !== null}
title={"Dodavanje intervjuera"}
subtitle={"Selekcija"}
title={t("selection.addInterviewer")}
subtitle={t("selection.selection")}
imgSrc={plus}
onClose={() => {
setActiveProcess(null);
@@ -113,8 +107,8 @@ const SelectionProcessPage = ({ history }) => {
/>
<CommentProcessDialog
open={activeProcessUnsuccess !== null}
title={"Komentar"}
subtitle={"Selekcija"}
title={t("candidates.comment")}
subtitle={t("selection.selection")}
imgSrc={forbiden}
onClose={() => {
setActiveProcessUnsuccess(null);
@@ -122,8 +116,8 @@ const SelectionProcessPage = ({ history }) => {
/>
<InterviewerDialog
open={activeInterview !== null}
title={"Promena intervjuera"}
subtitle={"Selekcija"}
title={t("selection.changeInterviewer")}
subtitle={t("selection.selection")}
imgSrc={plus}
onClose={() => {
setActiveInterview(null);
@@ -131,14 +125,13 @@ const SelectionProcessPage = ({ history }) => {
/>
<InterviewDialog
open={toggleInitModal}
title={"Dodavanje kandidata"}
subtitle={"HR intervju"}
title={t("selection.addCandidate")}
subtitle={t("selection.interview")}
imgSrc={plus}
onClose={() => {
setToggleInitModal(false);
}}
/>
<AddAdModal open={toggleModal} handleClose={handleToggleModal} />
<div className="selections">
<div className="level-header">
<h1>

+ 32
- 15
src/pages/StatsPage/StatsPage.js 파일 보기

@@ -17,7 +17,7 @@ import { returni18nLevel } from "../../util/helpers/stringHelpers";
// import ArchiveAd from "../../components/Ads/ArchiveAd";
// import { AD_DETAILS_PAGE } from "../../constants/pages";

const StatsPage = ({history}) => {
const StatsPage = ({ history }) => {
const dispatch = useDispatch();
const { stats } = useSelector((s) => s.stats);

@@ -80,18 +80,24 @@ const StatsPage = ({history}) => {
style={{ letterSpacing: "1px" }}
className="page-heading px36-heading"
>
Statistika
{t("stats.statistic")}
</h1>
<div className="stats-section">
<h2 className="section-header">Tok Selekcije</h2>
<h2 className="section-header">{t("stats.selectionFlow")}</h2>
<div className="stats-items">
{stats.levels &&
stats.levels.map((n) => (
<div key={n.level} className="stats-item" data-testid="stats-item">
<div
key={n.level}
className="stats-item"
data-testid="stats-item"
>
<div className="stats-item-content">
<h3>{n.countDone}</h3>
<p>
{returni18nLevel(n.level) != 'FD' && 'Obavljenih'}<br></br>{t('selectionLevels.done.'+returni18nLevel(n.level))}
{returni18nLevel(n.level) != "FD" && t("common.done")}
<br></br>
{t("selectionLevels.done." + returni18nLevel(n.level))}
</p>
</div>
<div className="bottom-static"></div>
@@ -100,11 +106,15 @@ const StatsPage = ({history}) => {
</div>
</div>
<div className="stats-section">
<h2 className="section-header">Odnosi</h2>
<h2 className="section-header">{t("stats.relations")}</h2>
<div className="stats-items-dynamic">
{stats.levels &&
stats.levels.map((n) => (
<div key={n.level} className="stats-item" data-testid="stats-item2">
<div
key={n.level}
className="stats-item"
data-testid="stats-item2"
>
<div className="stats-item-content">
<h3>
{n.countDone}
@@ -112,13 +122,17 @@ const StatsPage = ({history}) => {
{n.countAll}
</h3>
<p>
Broj {t('selectionLevels.done.'+returni18nLevel(n.level))}:<br></br>
Broj kontaktiranih:
{t("stats.number")}{" "}
{t("selectionLevels.done." + returni18nLevel(n.level))}:
<br></br>
{t("stats.contacted")}
</p>
</div>
<div className="bottom-dynamic">
<div
style={{ width: `${(n.countDone * 1.0 / n.countAll) * 100}%` }}
style={{
width: `${((n.countDone * 1.0) / n.countAll) * 100}%`,
}}
className="bottom-loader-indicator"
></div>
</div>
@@ -127,10 +141,10 @@ const StatsPage = ({history}) => {
</div>
</div>
<div className="stats-section">
<h2 className="section-header">Prijavljeni po pozicijama</h2>
<h2 className="section-header">{t("stats.registered")}</h2>
</div>
<div className="ads stat-ads">
{stats.ads && stats.ads.length > 0 && (
{stats.ads && stats.ads.length > 0 && (
<div className="archived-ads">
{/* <div className="archived-ads-header">
<h2>{t("ads.archiveAds")}</h2>
@@ -143,7 +157,10 @@ const StatsPage = ({history}) => {
<img src={arrow_left} alt="arrow-left" />
</button>
{stats.ads.length > 3 && (
<button onClick={arrowRightHandler} data-testid="right-arrow">
<button
onClick={arrowRightHandler}
data-testid="right-arrow"
>
<img src={arrow_right} alt="arrow-right" />
</button>
)}
@@ -160,7 +177,7 @@ const StatsPage = ({history}) => {
>
{stats.ads.map((ad, index) => (
<StatsAd
count={ad.count}
count={ad.count}
key={index}
title={ad.title}
minimumExperience={ad.minimumExperience}
@@ -182,7 +199,7 @@ const StatsPage = ({history}) => {
<img src={arrow_left} alt="arrow-left" />
</button>
{stats.ads.length > 2 && (
<button onClick={arrowRightHandler} data-testid="right-arrow">
<button onClick={arrowRightHandler} data-testid="right-arrow">
<img src={arrow_right} alt="arrow-right" />
</button>
)}

+ 168
- 149
src/pages/UsersPage/UserDetails.js 파일 보기

@@ -18,6 +18,7 @@ import { useEffect } from "react";
import { useTheme } from "@emotion/react";
import { useMediaQuery } from "@mui/material";
import ConfirmDialog from "../../components/MUI/ConfirmDialog";
import { useTranslation } from "react-i18next";

const UserDetails = ({ history }) => {
const theme = useTheme();
@@ -27,8 +28,9 @@ const UserDetails = ({ history }) => {
const [showConfirm, setConfirm] = useState(false);
const [showReset, setReset] = useState(false);
const [showDelete, setDelete] = useState(false);
const { t } = useTranslation();

const { user,errorMessage } = useSelector((s) => s.userDetails);
const { user, errorMessage } = useSelector((s) => s.userDetails);

const handleReset = (email) => {
dispatch(
@@ -84,10 +86,10 @@ const UserDetails = ({ history }) => {
<div className="r-b-rectangle"></div>
<ConfirmDialog
open={showReset}
title={"Reset password"}
title={t("users.resetPassword")}
subtitle={user?.firstName + " " + user?.lastName}
imgSrc={lock}
content="Are you sure you want to send password reset link?"
content={t("users.resetLink")}
onClose={() => {
setReset(false);
}}
@@ -98,10 +100,10 @@ const UserDetails = ({ history }) => {
/>
<ConfirmDialog
open={showConfirm}
title={"Disable user"}
title={t("users.disableUser")}
subtitle={user?.firstName + " " + user?.lastName}
imgSrc={forbiden}
content="Are you sure you want to disable user?"
content={t("users.questionDisableUser")}
onClose={() => {
setConfirm(false);
}}
@@ -112,10 +114,10 @@ const UserDetails = ({ history }) => {
/>
<ConfirmDialog
open={showDelete}
title={"Delete user"}
title={t("users.deleteUser")}
subtitle={user?.firstName + " " + user?.lastName}
imgSrc={filters}
content="Are you sure you want to delete user?"
content={t("users.questionDeleteUser")}
onClose={() => {
setDelete(false);
}}
@@ -125,156 +127,173 @@ const UserDetails = ({ history }) => {
}}
/>
<div className="pl-144 pt-36px">
{errorMessage ? errorMessage : <>
<div className="divider">
<div className="flex-center">
<h1 style={{ letterSpacing: "1px" }}>Korisnik</h1>
<div
className="vr"
style={{
margin: "0px 10px 0px 15px",
top: "6px",
backgroundColor: "#252525",
}}
></div>
<h3
style={{
letterSpacing: "0.75px",
position: "relative",
top: "4px",
}}
className="text-blue"
>
{user && user.firstName} {user && user.lastName}
</h3>
</div>
<div className="flex-center">
<IconButton
className={
"c-btn--primary-outlined c-btn userPageBtn ml-20px no-padding"
}
onClick={() => setReset(true)}
>
{!matches && "Resetuj password"}
<img
style={{
position: "relative",
top: -0.25,
paddingLeft: !matches && "10px",
}}
src={lock}
/>
</IconButton>
{!matches && (
<IconButton
className={`c-btn--primary-outlined c-btn userPageBtn ml-20px no-padding ${
user?.isEnabled ? "activeEnable" : "deactiveEnable"
}`}
onClick={() => setConfirm(true)}
>
{user?.isEnabled ? "Blokiraj" : "Odblokiraj"}
<img
{errorMessage ? (
errorMessage
) : (
<>
<div className="divider">
<div className="flex-center">
<h1 style={{ letterSpacing: "1px" }}>{t("users.user")}</h1>
<div
className="vr"
style={{
position: "relative",
top: -0.25,
paddingLeft: "10px",
margin: "0px 10px 0px 15px",
top: "6px",
backgroundColor: "#252525",
}}
src={forbiden}
/>
</IconButton>
)}
{!matches && (
<IconButton
className={
"c-btn--primary-outlined c-btn userPageBtn ml-20px no-padding"
}
onClick={() => setDelete(true)}
>
Obrisi
<img
></div>
<h3
style={{
letterSpacing: "0.75px",
position: "relative",
top: -0.25,
paddingLeft: "10px",
top: "4px",
}}
src={filters}
/>
</IconButton>
)}
<IconButton
className={
"c-btn--primary-outlined c-btn userPageBtn ml-20px no-padding"
}
className="text-blue"
>
{user && user.firstName} {user && user.lastName}
</h3>
</div>
<div className="flex-center">
<IconButton
className={
"c-btn--primary-outlined c-btn userPageBtn ml-20px no-padding"
}
onClick={() => setReset(true)}
>
{!matches && t("users.resetPassword")}
<img
style={{
position: "relative",
top: -0.25,
paddingLeft: !matches && "10px",
}}
src={lock}
/>
</IconButton>
{!matches && (
<IconButton
className={`c-btn--primary-outlined c-btn userPageBtn ml-20px no-padding ${
user?.isEnabled ? "activeEnable" : "deactiveEnable"
}`}
onClick={() => setConfirm(true)}
>
{user?.isEnabled ? t("users.block") : t("users.unblock")}
<img
style={{
position: "relative",
top: -0.25,
paddingLeft: "10px",
}}
src={forbiden}
/>
</IconButton>
)}
{!matches && (
<IconButton
className={
"c-btn--primary-outlined c-btn userPageBtn ml-20px no-padding"
}
onClick={() => setDelete(true)}
>
{t("common.delete")}
<img
style={{
position: "relative",
top: -0.25,
paddingLeft: "10px",
}}
src={filters}
/>
</IconButton>
)}
<IconButton
className={
"c-btn--primary-outlined c-btn userPageBtn ml-20px no-padding"
}
>
{!matches && t("users.profile")}
<img
style={{
position: "relative",
top: -0.25,
paddingLeft: !matches && "10px",
}}
src={filters}
/>
</IconButton>
</div>
</div>
<div
className="flex-center"
style={{ justifyContent: "flex-start" }}
>
{!matches && "Uredi profil"}
<img
style={{
position: "relative",
top: -0.25,
paddingLeft: !matches && "10px",
}}
src={filters}
src={avatar}
height="108px"
width="108px"
style={{ margin: "18px 15px 36px 0px" }}
/>
</IconButton>
</div>
</div>
<div className="flex-center" style={{ justifyContent: "flex-start" }}>
<img
src={avatar}
height="108px"
width="108px"
style={{ margin: "18px 15px 36px 0px" }}
/>
<p>
{user?.position
? user.position
: "Position has not been declared yet."}
</p>
</div>
<div style={{ display: "flex", flexDirection: "column", gap: "18px" }}>
<p style={{ fontWeight: "600" }}>Kontakt</p>
<div className="flex-center" style={{ justifyContent: "flex-start" }}>
<p style={{ width: "85px" }}>Email:</p>
<p className="text-blue">{user && user.email}</p>
</div>
<div className="flex-center" style={{ justifyContent: "flex-start" }}>
<p style={{ width: "85px" }}>Telefon:</p>
<p className="text-blue">
{user?.phoneNumber
? user.phoneNumber
: "User has no phone number saved."}
</p>
</div>
</div>
<div
style={{
display: "flex",
flexDirection: "column",
gap: "18px",
paddingTop: "36px",
}}
>
<p style={{ fontWeight: "600" }}>Drustvene mreze</p>
<div className="flex-center" style={{ justifyContent: "flex-start" }}>
<p style={{ width: "85px" }}>LinkedIn:</p>
<p className="text-blue">
{user?.linkedIn
? user.linkedIn
: "User takes not part in any social media."}
</p>
</div>
</div>
<div
style={{
display: "flex",
justifyContent: "flex-end",
marginTop: "150px",
}}
>
<Link to={"/users"} className="text-blue">
Nazad na listu korisnika
</Link>
</div></>}
<p>
{user?.position
? user.position
: t("users.positionNotDeclared")}
</p>
</div>
<div
style={{ display: "flex", flexDirection: "column", gap: "18px" }}
>
<p style={{ fontWeight: "600" }}>{t("users.contact")}</p>
<div
className="flex-center"
style={{ justifyContent: "flex-start" }}
>
<p style={{ width: "85px" }}>Email:</p>
<p className="text-blue">{user && user.email}</p>
</div>
<div
className="flex-center"
style={{ justifyContent: "flex-start" }}
>
<p style={{ width: "85px" }}>{t("users.phone")}:</p>
<p className="text-blue">
{user?.phoneNumber
? user.phoneNumber
: t("users.noPhoneNumber")}
</p>
</div>
</div>
<div
style={{
display: "flex",
flexDirection: "column",
gap: "18px",
paddingTop: "36px",
}}
>
<p style={{ fontWeight: "600" }}>{t("users.socials")}</p>
<div
className="flex-center"
style={{ justifyContent: "flex-start" }}
>
<p style={{ width: "85px" }}>LinkedIn:</p>
<p className="text-blue">
{user?.linkedIn ? user.linkedIn : t("users.noSocialMedia")}
</p>
</div>
</div>
<div
style={{
display: "flex",
justifyContent: "flex-end",
marginTop: "150px",
}}
>
<Link to={"/users"} className="text-blue">
{t("users.backToUsers")}
</Link>
</div>
</>
)}
</div>
</div>
);

+ 5
- 5
src/pages/UsersPage/UsersPage.js 파일 보기

@@ -180,10 +180,10 @@ const UsersPage = (props) => {
</div>
<ConfirmDialog
open={showConfirm}
title={"Disable user"}
title={t("users.disableUser")}
subtitle={chosen?.firstName + " " + chosen?.lastName}
imgSrc={forbiden}
content="Are you sure you want to disable user?"
content={t("users.questionDisableUser")}
onClose={() => {
setConfirm(false);
}}
@@ -193,10 +193,10 @@ const UsersPage = (props) => {
/>
<ConfirmDialog
open={showReset}
title={"Reset password"}
title={t("users.resetPassword")}
subtitle={chosen?.firstName + " " + chosen?.lastName}
imgSrc={lock}
content="Are you sure you want to send password reset link?"
content={t("users.resetLink")}
onClose={() => {
setReset(false);
}}
@@ -266,7 +266,7 @@ const UsersPage = (props) => {
}
onClick={handleChangeVisibility}
>
{!matches && "Pretraga"}
{!matches && t("candidates.search")}

<img
style={{

Loading…
취소
저장