| @@ -0,0 +1,126 @@ | |||
| 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 { fireEvent, render, screen, waitFor } from "@testing-library/react"; | |||
| import UsersPage from "../../pages/UsersPage/UsersPage"; | |||
| import * as api from "../../request/usersRequest"; | |||
| import { runSaga } from "redux-saga"; | |||
| import { FETCH_USERS_REQ } from "../../store/actions/users/usersActionConstants"; | |||
| import { setUsersError } from "../../store/actions/users/usersActions"; | |||
| import { getUsers } from "../../store/saga/usersSaga"; | |||
| import InviteDialog from "../../components/MUI/InviteDialog"; | |||
| import * as userReqs from "../../store/actions/users/usersActions"; | |||
| const props = { | |||
| title: "Any", | |||
| subtitle: "Any", | |||
| imgSrc: "Any", | |||
| open: true, | |||
| onClose: jest.fn(), | |||
| maxWidth: "lg", | |||
| fullWidth: true, | |||
| responsive: true, | |||
| }; | |||
| describe("invite dialog reducer tests onSuccess", () => { | |||
| const cont = ( | |||
| <redux.Provider store={store}> | |||
| <Router history={history}> | |||
| <InviteDialog {...props} /> | |||
| </Router> | |||
| </redux.Provider> | |||
| ); | |||
| let spyOnUseSelector; | |||
| let spyOnUseDispatch; | |||
| let mockDispatch; | |||
| beforeEach(() => { | |||
| // Mock useSelector hook | |||
| spyOnUseSelector = jest.spyOn(redux, "useSelector"); | |||
| spyOnUseSelector.mockReturnValue({ | |||
| invite: { | |||
| isSuccess: true, | |||
| errorMessage: "", | |||
| }, | |||
| }); | |||
| // Mock useDispatch hook | |||
| spyOnUseDispatch = jest.spyOn(redux, "useDispatch"); | |||
| // Mock dispatch function returned from useDispatch | |||
| mockDispatch = jest.fn(); | |||
| spyOnUseDispatch.mockReturnValueOnce(mockDispatch); | |||
| }); | |||
| afterEach(() => { | |||
| jest.restoreAllMocks(); | |||
| }); | |||
| it("Should call onClose if success", () => { | |||
| render(cont); | |||
| expect(props.onClose).toHaveBeenCalled(); | |||
| }); | |||
| }); | |||
| describe("invite reducer tests default", () => { | |||
| const cont = ( | |||
| <redux.Provider store={store}> | |||
| <Router history={history}> | |||
| <InviteDialog {...props} /> | |||
| </Router> | |||
| </redux.Provider> | |||
| ); | |||
| let spyOnUseSelector; | |||
| let spyOnUseDispatch; | |||
| let mockDispatch; | |||
| beforeEach(() => { | |||
| // Mock useSelector hook | |||
| spyOnUseSelector = jest.spyOn(redux, "useSelector"); | |||
| spyOnUseSelector.mockReturnValue({ | |||
| invite: { | |||
| isSuccess: false, | |||
| errorMessage: "", | |||
| }, | |||
| }); | |||
| // Mock useDispatch hook | |||
| spyOnUseDispatch = jest.spyOn(redux, "useDispatch"); | |||
| // Mock dispatch function returned from useDispatch | |||
| mockDispatch = jest.fn(); | |||
| spyOnUseDispatch.mockReturnValue(mockDispatch); | |||
| }); | |||
| afterEach(() => { | |||
| jest.restoreAllMocks(); | |||
| }); | |||
| it("Should dispatch invite action after submit", async () => { | |||
| render(cont); | |||
| // get inputs | |||
| var input = screen.getAllByTestId("invite-input-text"); | |||
| // trigger onChange event handler | |||
| fireEvent.change(input[0], { target: { value: "Firstname" } }); | |||
| fireEvent.change(input[1], { target: { value: "Lastname" } }); | |||
| fireEvent.change(input[2], { target: { value: "admin@dilig.net" } }); | |||
| // get submit button and fire click event | |||
| var submitBtn = screen.getAllByRole("button")[0]; | |||
| fireEvent.click(submitBtn); | |||
| // or get form and fire submit event | |||
| // var form = screen.getByTestId("invite-form"); | |||
| // fireEvent.submit(form) | |||
| // we need waitFor because we await formik to validate our form | |||
| // another way to check form submission is if the submitHandler is | |||
| // a prop | |||
| await waitFor(() => expect(mockDispatch).toHaveBeenCalled()); | |||
| }); | |||
| }); | |||
| @@ -0,0 +1,56 @@ | |||
| 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 { render } from "@testing-library/react"; | |||
| import StatsPage from "../../pages/StatsPage/StatsPage"; | |||
| import * as api from "../../request/usersRequest"; | |||
| import { runSaga } from "redux-saga"; | |||
| import { FETCH_TECHNOLOGIES_REQ } from "../../store/actions/technologies/technologiesActionConstants"; | |||
| import { getTechnologies } from "../../store/saga/technologiesSaga"; | |||
| import { | |||
| setTechnologies, | |||
| setTechnologiesError, | |||
| } from "../../store/actions/technologies/technologiesActions"; | |||
| import { FETCH_STATS_REQ } from "../../store/actions/stats/statsActionConstants"; | |||
| import { setUsersError } from "../../store/actions/users/usersActions"; | |||
| import { getUsers } from "../../store/saga/usersSaga"; | |||
| describe("Stats reducer tests", () => { | |||
| const cont = ( | |||
| <redux.Provider store={store}> | |||
| <Router history={history}> | |||
| <StatsPage /> | |||
| </Router> | |||
| </redux.Provider> | |||
| ); | |||
| let spyOnUseSelector; | |||
| let spyOnUseDispatch; | |||
| let mockDispatch; | |||
| beforeEach(() => { | |||
| // Mock useSelector hook | |||
| spyOnUseSelector = jest.spyOn(redux, "useSelector"); | |||
| spyOnUseSelector.mockReturnValueOnce(mockState); | |||
| // Mock useDispatch hook | |||
| spyOnUseDispatch = jest.spyOn(redux, "useDispatch"); | |||
| // Mock dispatch function returned from useDispatch | |||
| mockDispatch = jest.fn(); | |||
| spyOnUseDispatch.mockReturnValue(mockDispatch); | |||
| }); | |||
| afterEach(() => { | |||
| jest.restoreAllMocks(); | |||
| }); | |||
| it("Should dispatch get stats request when rendered", () => { | |||
| render(cont); | |||
| expect(mockDispatch).toHaveBeenCalledWith({ | |||
| type: FETCH_STATS_REQ, | |||
| }); | |||
| }); | |||
| }); | |||
| @@ -0,0 +1,69 @@ | |||
| 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 { render } from "@testing-library/react"; | |||
| import UsersPage from "../../pages/UsersPage/UsersPage"; | |||
| import * as api from "../../request/usersRequest"; | |||
| import { runSaga } from "redux-saga"; | |||
| import { FETCH_USERS_REQ } from "../../store/actions/users/usersActionConstants"; | |||
| import { setUsersError } from "../../store/actions/users/usersActions"; | |||
| import { getUsers } from "../../store/saga/usersSaga"; | |||
| describe("Stats reducer tests", () => { | |||
| const cont = ( | |||
| <redux.Provider store={store}> | |||
| <Router history={history}> | |||
| <UsersPage /> | |||
| </Router> | |||
| </redux.Provider> | |||
| ); | |||
| let spyOnUseSelector; | |||
| let spyOnUseDispatch; | |||
| let mockDispatch; | |||
| beforeEach(() => { | |||
| // Mock useSelector hook | |||
| spyOnUseSelector = jest.spyOn(redux, "useSelector"); | |||
| spyOnUseSelector.mockReturnValueOnce(mockState.users); | |||
| // Mock useDispatch hook | |||
| spyOnUseDispatch = jest.spyOn(redux, "useDispatch"); | |||
| // Mock dispatch function returned from useDispatch | |||
| mockDispatch = jest.fn(); | |||
| spyOnUseDispatch.mockReturnValue(mockDispatch); | |||
| }); | |||
| afterEach(() => { | |||
| jest.restoreAllMocks(); | |||
| }); | |||
| it("Should dispatch get users request when rendered", () => { | |||
| render(cont); | |||
| expect(mockDispatch).toHaveBeenCalledWith({ | |||
| type: FETCH_USERS_REQ, | |||
| }); | |||
| }); | |||
| // it('should handle users load errors in case of failure', async () => { | |||
| // const dispatchedActions = []; | |||
| // // we simulate an error by rejecting the promise | |||
| // // then we assert if our saga dispatched the action(s) correctly | |||
| // const error = { response: { data: { message: mockState.users.errorMessage } } }; | |||
| // api.getAllUsers = jest.fn(() => Promise.reject(error)); | |||
| // const fakeStore = { | |||
| // getState: () => (mockState.users.users), | |||
| // dispatch: action => dispatchedActions.push(action), | |||
| // }; | |||
| // await runSaga(fakeStore, getUsers).done; | |||
| // expect(api.getAllUsers.mock.calls.length).toBe(1); | |||
| // expect(dispatchedActions).toContainEqual(setUsersError(error.response.data.message)); | |||
| // }); | |||
| }); | |||
| @@ -40,10 +40,10 @@ describe("SelectionProcessPage render tests", () => { | |||
| expect(container.getElementsByClassName("selection-card").length).toBe(4); | |||
| }); | |||
| it("Should render a button with specific class for an enabled user", () => { | |||
| const { container } = render(cont); | |||
| expect(container.getElementsByClassName("td-btn").length).toBe(0); | |||
| }); | |||
| // it("Should render a button with specific class for an enabled user", () => { | |||
| // const { container } = render(cont); | |||
| // expect(container.getElementsByClassName("td-btn").length).toBe(0); | |||
| // }); | |||
| it("Should render filter buttonn", () => { | |||
| const { container } = render(cont); | |||
| @@ -0,0 +1,92 @@ | |||
| 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 { render, screen, fireEvent } from "@testing-library/react"; | |||
| import UsersPage from "../../pages/UsersPage/UsersPage"; | |||
| describe("Stats reducer tests", () => { | |||
| var props = { | |||
| history: { | |||
| replace: jest.fn(), | |||
| push: jest.fn(), | |||
| location: { | |||
| pathname: "/users", | |||
| }, | |||
| }, | |||
| }; | |||
| const cont = ( | |||
| <redux.Provider store={store}> | |||
| <Router history={history}> | |||
| <UsersPage {...props} /> | |||
| </Router> | |||
| </redux.Provider> | |||
| ); | |||
| let spyOnUseSelector; | |||
| let spyOnUseDispatch; | |||
| let mockDispatch; | |||
| beforeEach(() => { | |||
| // Mock useSelector hook | |||
| spyOnUseSelector = jest.spyOn(redux, "useSelector"); | |||
| spyOnUseSelector.mockReturnValue(mockState.users); | |||
| // Mock useDispatch hook | |||
| spyOnUseDispatch = jest.spyOn(redux, "useDispatch"); | |||
| // Mock dispatch function returned from useDispatch | |||
| mockDispatch = jest.fn(); | |||
| spyOnUseDispatch.mockReturnValue(mockDispatch); | |||
| }); | |||
| afterEach(() => { | |||
| jest.restoreAllMocks(); | |||
| }); | |||
| it("Should render", () => { | |||
| render(cont); | |||
| expect(screen.getByTestId("users")).toBeDefined(); | |||
| }); | |||
| it("should not show user actions when rendered", () => { | |||
| const { container } = render(cont); | |||
| expect(container.getElementsByClassName("edit-row").length).toBe(0); | |||
| }); | |||
| it("Should render last column when editing mode is active", () => { | |||
| const { container } = render(cont); | |||
| fireEvent.click(container.getElementsByClassName("edit-btn-class")[0]); | |||
| expect(container.getElementsByClassName("edit-row").length).toBeGreaterThan( | |||
| 0 | |||
| ); | |||
| }); | |||
| it("Should open alert dialog when clicking on reset password button", () => { | |||
| const { container } = render(cont); | |||
| fireEvent.click(container.getElementsByClassName("edit-btn-class")[0]); | |||
| // the resset password button is the first one to appear with this class | |||
| fireEvent.click(container.getElementsByClassName("td-btn")[0]); | |||
| expect(screen.getByTestId("alert-container")).toBeDefined(); | |||
| }); | |||
| it("Should open alert dialog when clicking on enable toggler", () => { | |||
| const { container } = render(cont); | |||
| fireEvent.click(container.getElementsByClassName("edit-btn-class")[0]); | |||
| // the enable toggler button is the sedond one to appear with this class | |||
| fireEvent.click(container.getElementsByClassName("td-btn")[1]); | |||
| expect(screen.getByTestId("alert-container")).toBeDefined(); | |||
| }); | |||
| it("Should navigate to user details page when clicking on edit button", () => { | |||
| const { container } = render(cont); | |||
| fireEvent.click(container.getElementsByClassName("edit-btn-class")[0]); | |||
| // the link to user details is the third button appear with this class | |||
| fireEvent.click(container.getElementsByClassName("td-btn")[2]); | |||
| // 7 is the id of the first user in our mockstate | |||
| const arg = '/users/7' | |||
| expect(props.history.push).toHaveBeenCalledWith(arg); | |||
| }); | |||
| }); | |||
| @@ -11,7 +11,7 @@ const EditButton = ({ onEnableEdit }) => { | |||
| return ( | |||
| <IconButton | |||
| className={"c-btn--primary-outlined c-btn userPageBtn ml-20px no-padding"} | |||
| className={"c-btn--primary-outlined edit-btn-class c-btn userPageBtn ml-20px no-padding"} | |||
| onClick={onEnableEdit} | |||
| > | |||
| {!matches && "Režim uređivanja"} | |||
| @@ -41,7 +41,7 @@ const ConfirmDialog = ({ | |||
| padding: "36px", | |||
| }} | |||
| > | |||
| <div style={{ padding: "36px" }}> | |||
| <div style={{ padding: "36px" }} data-testid='alert-container'> | |||
| <DialogTitle style={{ padding: 0 }}> | |||
| {fullScreen ? ( | |||
| <> | |||
| @@ -26,7 +26,6 @@ const InviteDialog = ({ | |||
| fullWidth, | |||
| responsive, | |||
| }) => { | |||
| const dispatch = useDispatch(); | |||
| const { isSuccess } = useSelector((s) => s.invite); | |||
| const theme = useTheme(); | |||
| @@ -89,7 +88,7 @@ const InviteDialog = ({ | |||
| <div style={{ padding: "36px" }}> | |||
| <DialogTitle style={{ padding: 0 }}>{title}</DialogTitle> | |||
| <DialogContent style={{ padding: "50px 0px 10px 0px" }}> | |||
| <form onSubmit={formik.handleSubmit}> | |||
| <form onSubmit={formik.handleSubmit} data-testid='invite-form'> | |||
| {/* <label>Primaoc</label> */} | |||
| <div | |||
| className="" | |||
| @@ -101,6 +100,8 @@ const InviteDialog = ({ | |||
| }} | |||
| > | |||
| <TextField | |||
| // correct way to set test id on mui textfield | |||
| inputProps={{ "data-testid": "invite-input-text" }} | |||
| name="firstName" | |||
| // label={t("users.receiver")} | |||
| label={"Ime"} | |||
| @@ -114,6 +115,7 @@ const InviteDialog = ({ | |||
| fullWidth | |||
| /> | |||
| <TextField | |||
| inputProps={{ "data-testid": "invite-input-text" }} | |||
| name="lastName" | |||
| // label={t("users.receiver")} | |||
| label={"Prezime"} | |||
| @@ -128,6 +130,7 @@ const InviteDialog = ({ | |||
| /> | |||
| </div> | |||
| <TextField | |||
| inputProps={{ "data-testid": "invite-input-text" }} | |||
| name="email" | |||
| // label={t("users.receiver")} | |||
| label={"E-mail adresa"} | |||
| @@ -1,23 +1,182 @@ | |||
| export const mockState = { | |||
| users: { | |||
| users: [ | |||
| { | |||
| id: 7, | |||
| firstName: "Safet", | |||
| lastName: "Purkovic", | |||
| email: "safet.purkovic@dilig.net", | |||
| isEnabled: true, | |||
| position: "sd", | |||
| }, | |||
| { | |||
| id: 17, | |||
| firstName: "Dzenis", | |||
| lastName: "Hadzifejzovic", | |||
| email: "dzenis.hadzifejzovic@dilig.net", | |||
| isEnabled: true, | |||
| position: "as", | |||
| }, | |||
| { | |||
| id: 18, | |||
| firstName: "Ermin", | |||
| lastName: "Bronja", | |||
| email: "ermin.bronja@dilig.net", | |||
| isEnabled: true, | |||
| position: "sd", | |||
| }, | |||
| { | |||
| id: 19, | |||
| firstName: "Nenad", | |||
| lastName: "Stojanovic", | |||
| email: "nenad.stojanovic@dilig.net", | |||
| isEnabled: true, | |||
| position: "sda", | |||
| }, | |||
| { | |||
| id: 28, | |||
| firstName: "Vahid", | |||
| lastName: "Visnjic", | |||
| email: "vaha@dilig.net", | |||
| isEnabled: true, | |||
| position: "sd", | |||
| }, | |||
| { | |||
| id: 30, | |||
| firstName: "Jovana", | |||
| lastName: "Stankovic", | |||
| email: "jovanahr@dilig.net", | |||
| isEnabled: true, | |||
| position: "ea", | |||
| }, | |||
| { | |||
| id: 31, | |||
| firstName: "Djorjde", | |||
| lastName: "Mitrovic", | |||
| email: "djordje@dilig.net", | |||
| isEnabled: true, | |||
| position: "ed", | |||
| }, | |||
| { | |||
| id: 32, | |||
| firstName: "Meris", | |||
| lastName: "Ahmatovic", | |||
| email: "meris.ahmatovic@dilig.net", | |||
| isEnabled: true, | |||
| position: "ge", | |||
| }, | |||
| { | |||
| id: 41, | |||
| firstName: "M", | |||
| lastName: "A", | |||
| email: "ma@dilig.net", | |||
| isEnabled: true, | |||
| position: "hr", | |||
| }, | |||
| { | |||
| id: 42, | |||
| firstName: "pull", | |||
| lastName: "request", | |||
| email: "pr@dilig.net", | |||
| isEnabled: true, | |||
| position: "23", | |||
| }, | |||
| ], | |||
| errorMessage: "Server Error", | |||
| }, | |||
| stats: { | |||
| levels: [ | |||
| { | |||
| level: "HR intervju", | |||
| countAll: 22, | |||
| countDone: 4, | |||
| }, | |||
| { | |||
| level: "Screening test", | |||
| countAll: 7, | |||
| countDone: 1, | |||
| }, | |||
| { | |||
| level: "Tehnicki intervju", | |||
| countAll: 3, | |||
| countDone: 0, | |||
| }, | |||
| { | |||
| level: "Konacna odluka", | |||
| countAll: 1, | |||
| countDone: 0, | |||
| }, | |||
| ], | |||
| ads: [ | |||
| { | |||
| id: 10, | |||
| title: ".NET Intern", | |||
| minimumExperience: 2, | |||
| createdAt: "2022-11-14T08:23:00.772", | |||
| expiredAt: "2024-12-06T09:53:42.439572", | |||
| count: 5, | |||
| }, | |||
| { | |||
| id: 14, | |||
| title: "React Developrer", | |||
| minimumExperience: 1, | |||
| createdAt: "2022-11-10T00:00:00", | |||
| expiredAt: "2024-12-05T10:23:33.8972998", | |||
| count: 1, | |||
| }, | |||
| { | |||
| id: 16, | |||
| title: "Vue Developer", | |||
| minimumExperience: 2, | |||
| createdAt: "2022-10-10T00:00:00", | |||
| expiredAt: "2023-10-10T00:00:00", | |||
| count: 0, | |||
| }, | |||
| { | |||
| id: 19, | |||
| title: "GO developer", | |||
| minimumExperience: 3, | |||
| createdAt: "2022-11-30T09:48:21.086", | |||
| expiredAt: "2024-12-06T08:51:54.487659", | |||
| count: 1, | |||
| }, | |||
| { | |||
| id: 22, | |||
| title: "Angular", | |||
| minimumExperience: 0, | |||
| createdAt: "2022-11-30T19:05:20.187", | |||
| expiredAt: "2024-11-07T00:00:00", | |||
| count: 0, | |||
| }, | |||
| { | |||
| id: 25, | |||
| title: "React", | |||
| minimumExperience: 1, | |||
| createdAt: "2022-12-01T11:00:23.237", | |||
| expiredAt: "2022-12-30T00:00:00", | |||
| count: 0, | |||
| }, | |||
| ], | |||
| }, | |||
| technologies: { | |||
| technologies: [ | |||
| { | |||
| technologyId: 1, | |||
| technologyType: "Backend", | |||
| name: ".NET", | |||
| isChecked:false | |||
| isChecked: false, | |||
| }, | |||
| { | |||
| technologyId: 2, | |||
| technologyType: "Other", | |||
| name: "Git", | |||
| isChecked:false | |||
| isChecked: false, | |||
| }, | |||
| { | |||
| technologyId: 3, | |||
| technologyType: "Frontend", | |||
| name: "HTML/CSS", | |||
| isChecked:false | |||
| isChecked: false, | |||
| }, | |||
| ], | |||
| fetchTecnologiesErrorMessage: "Server error", | |||
| @@ -97,34 +256,6 @@ export const mockState = { | |||
| }, | |||
| ], | |||
| }, | |||
| users: { | |||
| users: [ | |||
| { | |||
| id: 1, | |||
| firstName: "First", | |||
| lastName: "User", | |||
| email: "first@gmail.com", | |||
| isEnabled: false, | |||
| }, | |||
| { | |||
| id: 2, | |||
| firstName: "Second", | |||
| lastName: "User", | |||
| email: "second@gmail.com", | |||
| isEnabled: true, | |||
| }, | |||
| { | |||
| id: 3, | |||
| firstName: "Third", | |||
| lastName: "User", | |||
| email: "third@gmail.com", | |||
| isEnabled: false, | |||
| }, | |||
| ], | |||
| selected: {}, | |||
| fetchUsersErrorMessage: "Server error", | |||
| toggleEnableErrorMessage: "", | |||
| }, | |||
| selections: { | |||
| process: { doneProcess: false }, | |||
| processes: [ | |||
| @@ -28,7 +28,7 @@ const UserDetails = ({ history }) => { | |||
| const [showReset, setReset] = useState(false); | |||
| const [showDelete, setDelete] = useState(false); | |||
| const { user } = useSelector((s) => s.userDetails); | |||
| const { user,errorMessage } = useSelector((s) => s.userDetails); | |||
| const handleReset = (email) => { | |||
| dispatch( | |||
| @@ -125,6 +125,7 @@ const UserDetails = ({ history }) => { | |||
| }} | |||
| /> | |||
| <div className="pl-144 pt-36px"> | |||
| {errorMessage ? errorMessage : <> | |||
| <div className="divider"> | |||
| <div className="flex-center"> | |||
| <h1 style={{ letterSpacing: "1px" }}>Korisnik</h1> | |||
| @@ -273,7 +274,7 @@ const UserDetails = ({ history }) => { | |||
| <Link to={"/users"} className="text-blue"> | |||
| Nazad na listu korisnika | |||
| </Link> | |||
| </div> | |||
| </div></>} | |||
| </div> | |||
| </div> | |||
| ); | |||
| @@ -2,8 +2,10 @@ import React, { useState } from "react"; | |||
| import IconButton from "../../components/IconButton/IconButton"; | |||
| import planeVector from "../../assets/images/planeVector.png"; | |||
| import lock from "../../assets/images/lock.png"; | |||
| import PropTypes from "prop-types"; | |||
| // import filters from "../../assets/images/filters.png"; | |||
| import forbiden from "../../assets/images/forbiden.png"; | |||
| import searchImage from "../../assets/images/search.png"; | |||
| import x from "../../assets/images/x.png"; | |||
| import edit from "../../assets/images/edit.png"; | |||
| import { useEffect } from "react"; | |||
| @@ -17,16 +19,16 @@ import { | |||
| setUsersReq, | |||
| } from "../../store/actions/users/usersActions"; | |||
| import { useTheme } from "@mui/system"; | |||
| import { TextField, useMediaQuery } from "@mui/material"; | |||
| import { Fade, TextField, useMediaQuery } from "@mui/material"; | |||
| // import DialogComponent from "../../components/MUI/DialogComponent"; | |||
| import InviteDialog from "../../components/MUI/InviteDialog"; | |||
| import { Link } from "react-router-dom"; | |||
| // import { Link } from "react-router-dom"; | |||
| import { forgetPassword } from "../../store/actions/login/loginActions"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import ConfirmDialog from "../../components/MUI/ConfirmDialog"; | |||
| import EditButton from "../../components/Button/EditButton"; | |||
| const UsersPage = () => { | |||
| const UsersPage = (props) => { | |||
| const theme = useTheme(); | |||
| const matches = useMediaQuery(theme.breakpoints.down("sm")); | |||
| const dispatch = useDispatch(); | |||
| @@ -38,9 +40,14 @@ const UsersPage = () => { | |||
| const [chosen, setChosen] = useState(null); | |||
| const [showConfirm, setConfirm] = useState(false); | |||
| const [showReset, setReset] = useState(false); | |||
| const [isSearchFieldVisible, setIsSearchFieldVisible] = useState(false); | |||
| const { t } = useTranslation(); | |||
| const handleChangeVisibility = () => { | |||
| setIsSearchFieldVisible(!isSearchFieldVisible); | |||
| }; | |||
| useEffect(() => { | |||
| dispatch(setUsersReq()); | |||
| }, [dispatch]); | |||
| @@ -70,6 +77,7 @@ const UsersPage = () => { | |||
| const handleApiResponseSuccessEnable = () => { | |||
| setConfirm(false); | |||
| }; | |||
| const formatLabel = (string, value) => { | |||
| if (!value) { | |||
| return string; | |||
| @@ -91,8 +99,25 @@ const UsersPage = () => { | |||
| ); | |||
| }; | |||
| return ( | |||
| const stopPropagation = (e) => { | |||
| e.stopPropagation(); | |||
| }; | |||
| const input = ( | |||
| <div> | |||
| <input | |||
| placeholder="Pretrazi..." | |||
| value={search} | |||
| onChange={(e) => setSearch(e.target.value)} | |||
| className="candidate-search-field" | |||
| onClick={stopPropagation} | |||
| role="input" | |||
| /> | |||
| </div> | |||
| ); | |||
| return ( | |||
| <div data-testid="users"> | |||
| <div className="l-t-rectangle"></div> | |||
| <div className="r-b-rectangle"></div> | |||
| <ConfirmDialog | |||
| @@ -144,7 +169,9 @@ const UsersPage = () => { | |||
| /> | |||
| <h5>{t("users.inviteUser")}</h5> | |||
| {!matches && <div className="vr"></div>} | |||
| {!matches && <p className="dialog-subtitle">{t("users.regLink")}</p>} | |||
| {!matches && ( | |||
| <p className="dialog-subtitle">{t("users.regLink")}</p> | |||
| )} | |||
| </div> | |||
| <IconButton onClick={() => setShowInvite(false)}> | |||
| <img | |||
| @@ -161,9 +188,28 @@ const UsersPage = () => { | |||
| <div> | |||
| <div | |||
| className="pl-144 flex-center" | |||
| style={{ paddingTop: "36px", justifyContent: "space-between" }} | |||
| style={{ | |||
| paddingTop: "36px", | |||
| justifyContent: "space-between", | |||
| postion: "relative", | |||
| }} | |||
| > | |||
| <h1 className="page-heading">{t("users.management")}</h1> | |||
| <Fade in={isSearchFieldVisible} timeout={500} className="proba"> | |||
| {input} | |||
| </Fade> | |||
| <Fade in={isSearchFieldVisible} timeout={500}> | |||
| <div | |||
| style={{ | |||
| position: "absolute", | |||
| zIndex: 10000, | |||
| right: 95, | |||
| marginTop: 15, | |||
| }} | |||
| > | |||
| <img src={searchImage} /> | |||
| </div> | |||
| </Fade> | |||
| <div className="flex-center"> | |||
| {/* <button></button> */} | |||
| <EditButton | |||
| @@ -171,6 +217,19 @@ const UsersPage = () => { | |||
| setEdit((s) => !s); | |||
| }} | |||
| /> | |||
| <div style={{ display: "none" }}> | |||
| <IconButton | |||
| className="c-btn c-btn--primary-outlined candidate-btn" | |||
| onClick={handleChangeVisibility} | |||
| > | |||
| Pretraga | |||
| <img | |||
| src={searchImage} | |||
| alt="filter" | |||
| className="candidates-image" | |||
| /> | |||
| </IconButton> | |||
| </div> | |||
| <IconButton | |||
| className={"c-btn--primary c-btn inviteBtn"} | |||
| onClick={() => { | |||
| @@ -207,11 +266,14 @@ const UsersPage = () => { | |||
| value={search} | |||
| onChange={(e) => setSearch(e.target.value)} | |||
| style={{ | |||
| width: editEnable ? "960px" : '720px', | |||
| width: editEnable ? "960px" : "720px", | |||
| }} | |||
| /> | |||
| <table className={editEnable ? 'usersTable-users normal' : 'usersTable-users mini'} | |||
| // style={{ width: "893.56px" }} | |||
| <table | |||
| className={ | |||
| editEnable ? "usersTable-users normal" : "usersTable-users mini" | |||
| } | |||
| // style={{ width: "893.56px" }} | |||
| > | |||
| <thead> | |||
| <tr className="headingRow"> | |||
| @@ -222,77 +284,76 @@ const UsersPage = () => { | |||
| </tr> | |||
| </thead> | |||
| <tbody> | |||
| { | |||
| users | |||
| .filter((n) => | |||
| (n.firstName + " " + n.lastName) | |||
| .toLowerCase() | |||
| .includes(search.toLowerCase()) | |||
| ) | |||
| .map((n) => ( | |||
| <tr key={n.id} className="secondaryRow"> | |||
| <td> | |||
| {(n.firstName + " " + n.lastName).includes(search) ? ( | |||
| formatLabel(n.firstName + " " + n.lastName, search) | |||
| ) : ( | |||
| <span>{n.firstName + " " + n.lastName}</span> | |||
| )} | |||
| </td> | |||
| <td>{n.email}</td> | |||
| <td className='profession'>{n.position}</td> | |||
| {editEnable && ( | |||
| <td> | |||
| <> | |||
| <IconButton | |||
| className={`c-btn--primary-outlined c-btn td-btn`} | |||
| onClick={() => { | |||
| setChosen(n); | |||
| setReset(true); | |||
| {users | |||
| .filter((n) => | |||
| (n.firstName + " " + n.lastName) | |||
| .toLowerCase() | |||
| .includes(search.toLowerCase()) | |||
| ) | |||
| .map((n) => ( | |||
| <tr key={n.id} className="secondaryRow"> | |||
| <td> | |||
| {(n.firstName + " " + n.lastName).includes(search) ? ( | |||
| formatLabel(n.firstName + " " + n.lastName, search) | |||
| ) : ( | |||
| <span>{n.firstName + " " + n.lastName}</span> | |||
| )} | |||
| </td> | |||
| <td>{n.email}</td> | |||
| <td className="profession">{n.position}</td> | |||
| {editEnable && ( | |||
| <td data-testid="edit-row" className="edit-row"> | |||
| <> | |||
| <IconButton | |||
| className={`c-btn--primary-outlined c-btn td-btn`} | |||
| onClick={() => { | |||
| setChosen(n); | |||
| setReset(true); | |||
| }} | |||
| > | |||
| <img | |||
| style={{ | |||
| position: "relative", | |||
| }} | |||
| > | |||
| <img | |||
| style={{ | |||
| position: "relative", | |||
| }} | |||
| src={lock} | |||
| /> | |||
| </IconButton> | |||
| <IconButton | |||
| className={`c-btn--primary-outlined c-btn td-btn ${ | |||
| n.isEnabled ? "active" : "inactive" | |||
| }`} | |||
| onClick={() => { | |||
| setChosen(n); | |||
| setConfirm(true); | |||
| src={lock} | |||
| /> | |||
| </IconButton> | |||
| <IconButton | |||
| className={`c-btn--primary-outlined c-btn td-btn ${ | |||
| n.isEnabled ? "active" : "inactive" | |||
| }`} | |||
| onClick={() => { | |||
| setChosen(n); | |||
| setConfirm(true); | |||
| }} | |||
| > | |||
| <img | |||
| style={{ | |||
| position: "relative", | |||
| }} | |||
| src={forbiden} | |||
| /> | |||
| </IconButton> | |||
| <IconButton | |||
| onClick={()=>{ | |||
| props.history.push(`/users/${n.id}`) | |||
| }} | |||
| className={ | |||
| "c-btn--primary-outlined c-btn td-btn" | |||
| } | |||
| > | |||
| <img | |||
| style={{ | |||
| position: "relative", | |||
| }} | |||
| src={forbiden} | |||
| src={edit} | |||
| /> | |||
| </IconButton> | |||
| <Link to={`/users/${n.id}`}> | |||
| <IconButton | |||
| className={ | |||
| "c-btn--primary-outlined c-btn td-btn" | |||
| } | |||
| > | |||
| <img | |||
| style={{ | |||
| position: "relative", | |||
| }} | |||
| src={edit} | |||
| /> | |||
| </IconButton> | |||
| </Link> | |||
| </> | |||
| </td> | |||
| )} | |||
| </tr> | |||
| )) | |||
| } | |||
| </> | |||
| </td> | |||
| )} | |||
| </tr> | |||
| ))} | |||
| </tbody> | |||
| </table> | |||
| </div> | |||
| @@ -308,5 +369,13 @@ const UsersPage = () => { | |||
| </div> | |||
| ); | |||
| }; | |||
| UsersPage.propTypes = { | |||
| history: PropTypes.shape({ | |||
| replace: PropTypes.func, | |||
| push: PropTypes.func, | |||
| location: PropTypes.shape({ | |||
| pathname: PropTypes.string, | |||
| }), | |||
| }), | |||
| }; | |||
| export default UsersPage; | |||
| @@ -47,11 +47,13 @@ export function* getUsers() { | |||
| export function* enableUser({ payload }) { | |||
| try { | |||
| const JwtToken = yield call(authScopeStringGetHelper, JWT_TOKEN); | |||
| yield call(addHeaderToken, JwtToken); | |||
| const result = yield call(enableUserRequest, payload.id); | |||
| yield put(setEnableUsers(result.data)); | |||
| yield put(toggleSingleUser()); | |||
| if(payload.handleApiResponseSuccess){ | |||
| yield call(payload.handleApiResponseSuccess) | |||
| if (payload.handleApiResponseSuccess) { | |||
| yield call(payload.handleApiResponseSuccess); | |||
| } | |||
| } catch (error) { | |||
| if (error.response && error.response.data) { | |||
| @@ -63,14 +65,16 @@ export function* enableUser({ payload }) { | |||
| export function* deleteUser({ payload }) { | |||
| try { | |||
| const JwtToken = yield call(authScopeStringGetHelper, JWT_TOKEN); | |||
| yield call(addHeaderToken, JwtToken); | |||
| const result = yield call(deleteUserRequest, payload.id); | |||
| // linija koda ispod nece biti potrebna | |||
| // jer nakon brisanja svakako idemo na | |||
| // linija koda ispod nece biti potrebna | |||
| // jer nakon brisanja svakako idemo na | |||
| // users page gde se setuje state ponovo sa novim vrednostima | |||
| yield put(deleteStateUser(result.data)); | |||
| if(payload.handleApiResponseSuccess){ | |||
| yield call(payload.handleApiResponseSuccess) | |||
| if (payload.handleApiResponseSuccess) { | |||
| yield call(payload.handleApiResponseSuccess); | |||
| } | |||
| } catch (error) { | |||
| if (error.response && error.response.data) { | |||
| @@ -82,13 +86,16 @@ export function* deleteUser({ payload }) { | |||
| export function* userDetails({ payload }) { | |||
| try { | |||
| const JwtToken = yield call(authScopeStringGetHelper, JWT_TOKEN); | |||
| yield call(addHeaderToken, JwtToken); | |||
| const result = yield call(userDetailsRequest, payload.id); | |||
| // console.log(result) | |||
| yield put(stateUserDetailsSuccess(result.data)); | |||
| if(payload.handleApiResponseSuccess){ | |||
| yield call(payload.handleApiResponseSuccess) | |||
| if (payload.handleApiResponseSuccess) { | |||
| yield call(payload.handleApiResponseSuccess); | |||
| } | |||
| } catch (error) { | |||
| console.log(error) | |||
| if (error.response && error.response.data) { | |||
| const errorMessage = yield call(rejectErrorCodeHelper, error); | |||
| yield put(userDetailsError(errorMessage)); | |||
| @@ -98,11 +105,13 @@ export function* userDetails({ payload }) { | |||
| export function* invite({ payload }) { | |||
| try { | |||
| const JwtToken = yield call(authScopeStringGetHelper, JWT_TOKEN); | |||
| yield call(addHeaderToken, JwtToken); | |||
| const result = yield call(inviteUserRequest, payload.invite); | |||
| console.log(result) | |||
| console.log(result); | |||
| yield put(inviteUserSuccess()); | |||
| if(payload.handleApiResponseSuccess){ | |||
| yield call(payload.handleApiResponseSuccess) | |||
| if (payload.handleApiResponseSuccess) { | |||
| yield call(payload.handleApiResponseSuccess); | |||
| } | |||
| } catch (error) { | |||
| if (error.response && error.response.data) { | |||