| 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()); | |||||
| }); | |||||
| }); |
| 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, | |||||
| }); | |||||
| }); | |||||
| }); |
| 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)); | |||||
| // }); | |||||
| }); |
| expect(container.getElementsByClassName("selection-card").length).toBe(4); | 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", () => { | it("Should render filter buttonn", () => { | ||||
| const { container } = render(cont); | const { container } = render(cont); |
| 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); | |||||
| }); | |||||
| }); |
| return ( | return ( | ||||
| <IconButton | <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} | onClick={onEnableEdit} | ||||
| > | > | ||||
| {!matches && "Režim uređivanja"} | {!matches && "Režim uređivanja"} |
| padding: "36px", | padding: "36px", | ||||
| }} | }} | ||||
| > | > | ||||
| <div style={{ padding: "36px" }}> | |||||
| <div style={{ padding: "36px" }} data-testid='alert-container'> | |||||
| <DialogTitle style={{ padding: 0 }}> | <DialogTitle style={{ padding: 0 }}> | ||||
| {fullScreen ? ( | {fullScreen ? ( | ||||
| <> | <> |
| fullWidth, | fullWidth, | ||||
| responsive, | responsive, | ||||
| }) => { | }) => { | ||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| const { isSuccess } = useSelector((s) => s.invite); | const { isSuccess } = useSelector((s) => s.invite); | ||||
| const theme = useTheme(); | const theme = useTheme(); | ||||
| <div style={{ padding: "36px" }}> | <div style={{ padding: "36px" }}> | ||||
| <DialogTitle style={{ padding: 0 }}>{title}</DialogTitle> | <DialogTitle style={{ padding: 0 }}>{title}</DialogTitle> | ||||
| <DialogContent style={{ padding: "50px 0px 10px 0px" }}> | <DialogContent style={{ padding: "50px 0px 10px 0px" }}> | ||||
| <form onSubmit={formik.handleSubmit}> | |||||
| <form onSubmit={formik.handleSubmit} data-testid='invite-form'> | |||||
| {/* <label>Primaoc</label> */} | {/* <label>Primaoc</label> */} | ||||
| <div | <div | ||||
| className="" | className="" | ||||
| }} | }} | ||||
| > | > | ||||
| <TextField | <TextField | ||||
| // correct way to set test id on mui textfield | |||||
| inputProps={{ "data-testid": "invite-input-text" }} | |||||
| name="firstName" | name="firstName" | ||||
| // label={t("users.receiver")} | // label={t("users.receiver")} | ||||
| label={"Ime"} | label={"Ime"} | ||||
| fullWidth | fullWidth | ||||
| /> | /> | ||||
| <TextField | <TextField | ||||
| inputProps={{ "data-testid": "invite-input-text" }} | |||||
| name="lastName" | name="lastName" | ||||
| // label={t("users.receiver")} | // label={t("users.receiver")} | ||||
| label={"Prezime"} | label={"Prezime"} | ||||
| /> | /> | ||||
| </div> | </div> | ||||
| <TextField | <TextField | ||||
| inputProps={{ "data-testid": "invite-input-text" }} | |||||
| name="email" | name="email" | ||||
| // label={t("users.receiver")} | // label={t("users.receiver")} | ||||
| label={"E-mail adresa"} | label={"E-mail adresa"} |
| export const mockState = { | 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: { | ||||
| technologies: [ | technologies: [ | ||||
| { | { | ||||
| technologyId: 1, | technologyId: 1, | ||||
| technologyType: "Backend", | technologyType: "Backend", | ||||
| name: ".NET", | name: ".NET", | ||||
| isChecked:false | |||||
| isChecked: false, | |||||
| }, | }, | ||||
| { | { | ||||
| technologyId: 2, | technologyId: 2, | ||||
| technologyType: "Other", | technologyType: "Other", | ||||
| name: "Git", | name: "Git", | ||||
| isChecked:false | |||||
| isChecked: false, | |||||
| }, | }, | ||||
| { | { | ||||
| technologyId: 3, | technologyId: 3, | ||||
| technologyType: "Frontend", | technologyType: "Frontend", | ||||
| name: "HTML/CSS", | name: "HTML/CSS", | ||||
| isChecked:false | |||||
| isChecked: false, | |||||
| }, | }, | ||||
| ], | ], | ||||
| fetchTecnologiesErrorMessage: "Server error", | fetchTecnologiesErrorMessage: "Server error", | ||||
| }, | }, | ||||
| ], | ], | ||||
| }, | }, | ||||
| 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: { | selections: { | ||||
| process: { doneProcess: false }, | process: { doneProcess: false }, | ||||
| processes: [ | processes: [ |
| const [showReset, setReset] = useState(false); | const [showReset, setReset] = useState(false); | ||||
| const [showDelete, setDelete] = useState(false); | const [showDelete, setDelete] = useState(false); | ||||
| const { user } = useSelector((s) => s.userDetails); | |||||
| const { user,errorMessage } = useSelector((s) => s.userDetails); | |||||
| const handleReset = (email) => { | const handleReset = (email) => { | ||||
| dispatch( | dispatch( | ||||
| }} | }} | ||||
| /> | /> | ||||
| <div className="pl-144 pt-36px"> | <div className="pl-144 pt-36px"> | ||||
| {errorMessage ? errorMessage : <> | |||||
| <div className="divider"> | <div className="divider"> | ||||
| <div className="flex-center"> | <div className="flex-center"> | ||||
| <h1 style={{ letterSpacing: "1px" }}>Korisnik</h1> | <h1 style={{ letterSpacing: "1px" }}>Korisnik</h1> | ||||
| <Link to={"/users"} className="text-blue"> | <Link to={"/users"} className="text-blue"> | ||||
| Nazad na listu korisnika | Nazad na listu korisnika | ||||
| </Link> | </Link> | ||||
| </div> | |||||
| </div></>} | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| ); | ); |
| import IconButton from "../../components/IconButton/IconButton"; | import IconButton from "../../components/IconButton/IconButton"; | ||||
| import planeVector from "../../assets/images/planeVector.png"; | import planeVector from "../../assets/images/planeVector.png"; | ||||
| import lock from "../../assets/images/lock.png"; | import lock from "../../assets/images/lock.png"; | ||||
| import PropTypes from "prop-types"; | |||||
| // import filters from "../../assets/images/filters.png"; | // import filters from "../../assets/images/filters.png"; | ||||
| import forbiden from "../../assets/images/forbiden.png"; | import forbiden from "../../assets/images/forbiden.png"; | ||||
| import searchImage from "../../assets/images/search.png"; | |||||
| import x from "../../assets/images/x.png"; | import x from "../../assets/images/x.png"; | ||||
| import edit from "../../assets/images/edit.png"; | import edit from "../../assets/images/edit.png"; | ||||
| import { useEffect } from "react"; | import { useEffect } from "react"; | ||||
| setUsersReq, | setUsersReq, | ||||
| } from "../../store/actions/users/usersActions"; | } from "../../store/actions/users/usersActions"; | ||||
| import { useTheme } from "@mui/system"; | 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 DialogComponent from "../../components/MUI/DialogComponent"; | ||||
| import InviteDialog from "../../components/MUI/InviteDialog"; | 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 { forgetPassword } from "../../store/actions/login/loginActions"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import ConfirmDialog from "../../components/MUI/ConfirmDialog"; | import ConfirmDialog from "../../components/MUI/ConfirmDialog"; | ||||
| import EditButton from "../../components/Button/EditButton"; | import EditButton from "../../components/Button/EditButton"; | ||||
| const UsersPage = () => { | |||||
| const UsersPage = (props) => { | |||||
| const theme = useTheme(); | const theme = useTheme(); | ||||
| const matches = useMediaQuery(theme.breakpoints.down("sm")); | const matches = useMediaQuery(theme.breakpoints.down("sm")); | ||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| const [chosen, setChosen] = useState(null); | const [chosen, setChosen] = useState(null); | ||||
| const [showConfirm, setConfirm] = useState(false); | const [showConfirm, setConfirm] = useState(false); | ||||
| const [showReset, setReset] = useState(false); | const [showReset, setReset] = useState(false); | ||||
| const [isSearchFieldVisible, setIsSearchFieldVisible] = useState(false); | |||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const handleChangeVisibility = () => { | |||||
| setIsSearchFieldVisible(!isSearchFieldVisible); | |||||
| }; | |||||
| useEffect(() => { | useEffect(() => { | ||||
| dispatch(setUsersReq()); | dispatch(setUsersReq()); | ||||
| }, [dispatch]); | }, [dispatch]); | ||||
| const handleApiResponseSuccessEnable = () => { | const handleApiResponseSuccessEnable = () => { | ||||
| setConfirm(false); | setConfirm(false); | ||||
| }; | }; | ||||
| const formatLabel = (string, value) => { | const formatLabel = (string, value) => { | ||||
| if (!value) { | if (!value) { | ||||
| return string; | return string; | ||||
| ); | ); | ||||
| }; | }; | ||||
| return ( | |||||
| const stopPropagation = (e) => { | |||||
| e.stopPropagation(); | |||||
| }; | |||||
| const input = ( | |||||
| <div> | <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="l-t-rectangle"></div> | ||||
| <div className="r-b-rectangle"></div> | <div className="r-b-rectangle"></div> | ||||
| <ConfirmDialog | <ConfirmDialog | ||||
| /> | /> | ||||
| <h5>{t("users.inviteUser")}</h5> | <h5>{t("users.inviteUser")}</h5> | ||||
| {!matches && <div className="vr"></div>} | {!matches && <div className="vr"></div>} | ||||
| {!matches && <p className="dialog-subtitle">{t("users.regLink")}</p>} | |||||
| {!matches && ( | |||||
| <p className="dialog-subtitle">{t("users.regLink")}</p> | |||||
| )} | |||||
| </div> | </div> | ||||
| <IconButton onClick={() => setShowInvite(false)}> | <IconButton onClick={() => setShowInvite(false)}> | ||||
| <img | <img | ||||
| <div> | <div> | ||||
| <div | <div | ||||
| className="pl-144 flex-center" | 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> | <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"> | <div className="flex-center"> | ||||
| {/* <button></button> */} | {/* <button></button> */} | ||||
| <EditButton | <EditButton | ||||
| setEdit((s) => !s); | 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 | <IconButton | ||||
| className={"c-btn--primary c-btn inviteBtn"} | className={"c-btn--primary c-btn inviteBtn"} | ||||
| onClick={() => { | onClick={() => { | ||||
| value={search} | value={search} | ||||
| onChange={(e) => setSearch(e.target.value)} | onChange={(e) => setSearch(e.target.value)} | ||||
| style={{ | 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> | <thead> | ||||
| <tr className="headingRow"> | <tr className="headingRow"> | ||||
| </tr> | </tr> | ||||
| </thead> | </thead> | ||||
| <tbody> | <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 | <img | ||||
| style={{ | style={{ | ||||
| position: "relative", | position: "relative", | ||||
| }} | }} | ||||
| src={forbiden} | |||||
| src={edit} | |||||
| /> | /> | ||||
| </IconButton> | </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> | </tbody> | ||||
| </table> | </table> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| ); | ); | ||||
| }; | }; | ||||
| UsersPage.propTypes = { | |||||
| history: PropTypes.shape({ | |||||
| replace: PropTypes.func, | |||||
| push: PropTypes.func, | |||||
| location: PropTypes.shape({ | |||||
| pathname: PropTypes.string, | |||||
| }), | |||||
| }), | |||||
| }; | |||||
| export default UsersPage; | export default UsersPage; |
| export function* enableUser({ payload }) { | export function* enableUser({ payload }) { | ||||
| try { | try { | ||||
| const JwtToken = yield call(authScopeStringGetHelper, JWT_TOKEN); | |||||
| yield call(addHeaderToken, JwtToken); | |||||
| const result = yield call(enableUserRequest, payload.id); | const result = yield call(enableUserRequest, payload.id); | ||||
| yield put(setEnableUsers(result.data)); | yield put(setEnableUsers(result.data)); | ||||
| yield put(toggleSingleUser()); | yield put(toggleSingleUser()); | ||||
| if(payload.handleApiResponseSuccess){ | |||||
| yield call(payload.handleApiResponseSuccess) | |||||
| if (payload.handleApiResponseSuccess) { | |||||
| yield call(payload.handleApiResponseSuccess); | |||||
| } | } | ||||
| } catch (error) { | } catch (error) { | ||||
| if (error.response && error.response.data) { | if (error.response && error.response.data) { | ||||
| export function* deleteUser({ payload }) { | export function* deleteUser({ payload }) { | ||||
| try { | try { | ||||
| const JwtToken = yield call(authScopeStringGetHelper, JWT_TOKEN); | |||||
| yield call(addHeaderToken, JwtToken); | |||||
| const result = yield call(deleteUserRequest, payload.id); | 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 | // users page gde se setuje state ponovo sa novim vrednostima | ||||
| yield put(deleteStateUser(result.data)); | yield put(deleteStateUser(result.data)); | ||||
| if(payload.handleApiResponseSuccess){ | |||||
| yield call(payload.handleApiResponseSuccess) | |||||
| if (payload.handleApiResponseSuccess) { | |||||
| yield call(payload.handleApiResponseSuccess); | |||||
| } | } | ||||
| } catch (error) { | } catch (error) { | ||||
| if (error.response && error.response.data) { | if (error.response && error.response.data) { | ||||
| export function* userDetails({ payload }) { | export function* userDetails({ payload }) { | ||||
| try { | try { | ||||
| const JwtToken = yield call(authScopeStringGetHelper, JWT_TOKEN); | |||||
| yield call(addHeaderToken, JwtToken); | |||||
| const result = yield call(userDetailsRequest, payload.id); | const result = yield call(userDetailsRequest, payload.id); | ||||
| // console.log(result) | // console.log(result) | ||||
| yield put(stateUserDetailsSuccess(result.data)); | yield put(stateUserDetailsSuccess(result.data)); | ||||
| if(payload.handleApiResponseSuccess){ | |||||
| yield call(payload.handleApiResponseSuccess) | |||||
| if (payload.handleApiResponseSuccess) { | |||||
| yield call(payload.handleApiResponseSuccess); | |||||
| } | } | ||||
| } catch (error) { | } catch (error) { | ||||
| console.log(error) | |||||
| if (error.response && error.response.data) { | if (error.response && error.response.data) { | ||||
| const errorMessage = yield call(rejectErrorCodeHelper, error); | const errorMessage = yield call(rejectErrorCodeHelper, error); | ||||
| yield put(userDetailsError(errorMessage)); | yield put(userDetailsError(errorMessage)); | ||||
| export function* invite({ payload }) { | export function* invite({ payload }) { | ||||
| try { | try { | ||||
| const JwtToken = yield call(authScopeStringGetHelper, JWT_TOKEN); | |||||
| yield call(addHeaderToken, JwtToken); | |||||
| const result = yield call(inviteUserRequest, payload.invite); | const result = yield call(inviteUserRequest, payload.invite); | ||||
| console.log(result) | |||||
| console.log(result); | |||||
| yield put(inviteUserSuccess()); | yield put(inviteUserSuccess()); | ||||
| if(payload.handleApiResponseSuccess){ | |||||
| yield call(payload.handleApiResponseSuccess) | |||||
| if (payload.handleApiResponseSuccess) { | |||||
| yield call(payload.handleApiResponseSuccess); | |||||
| } | } | ||||
| } catch (error) { | } catch (error) { | ||||
| if (error.response && error.response.data) { | if (error.response && error.response.data) { |