Adding
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
import ProfilePage from "./ProfilePage";
|
||||
import { IEmployee } from "../../shared/models/Employee";
|
||||
import ProfileView from "../../components/profile/ProfileView";
|
||||
|
||||
jest.mock("react-redux");
|
||||
|
||||
describe("A component for displaying appUser profile", () => {
|
||||
it("Displays a valid profile", () => {
|
||||
const profile: IEmployee = {
|
||||
adEmail: "[email protected]",
|
||||
personId: 11,
|
||||
employeeId: "11771",
|
||||
homeDepartment: "HOME",
|
||||
baseRole: "QA",
|
||||
fullName: "John Tester",
|
||||
additionalPermissions: [],
|
||||
role: {
|
||||
name: "Role1",
|
||||
description: "NONE",
|
||||
permissions: [{ code: "VIEW_DASHBOARD", description: "" }],
|
||||
hasCommissions: true
|
||||
},
|
||||
departments: [],
|
||||
assignments: []
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
const refreshProfile = () => {};
|
||||
|
||||
let wrapper = shallow(
|
||||
<ProfilePage profile={profile} refreshProfile={refreshProfile} />
|
||||
);
|
||||
|
||||
const found = wrapper.find(ProfileView);
|
||||
expect(found.length).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,208 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import ProfileView from "../../components/profile/ProfileView";
|
||||
import { Alert, Col, ListGroup, ListGroupItem, Row } from "reactstrap";
|
||||
import EmployeePage from "../employee-page/EmployeePage";
|
||||
import PermissionsPage from "../../components/permission/PermissionsPage";
|
||||
import PayoutPage from "../payouts/PayoutPage";
|
||||
import {
|
||||
IEmployee,
|
||||
IEmployeeDetailsUpdate
|
||||
} from "../../shared/models/Employee";
|
||||
import { SettingsContext } from "../../shared/providers/SettingsProvider";
|
||||
import PermissionGuard from "../shared/PermissionGuard";
|
||||
import EmployeeStatusGuard from "../shared/EmployeeStatusGuard";
|
||||
import moment from "moment";
|
||||
import EligiblePayouts from "../payout-profile/EligiblePayouts";
|
||||
import EmployeeDetailsPage from "../employee-page/EmployeeDetailsPage";
|
||||
import IAsyncStateContainer from "../../shared/types/IAsyncStateContainer";
|
||||
|
||||
interface IProfileParams {
|
||||
profile: IEmployee;
|
||||
employeeDetailsUpdateResult: string;
|
||||
submitStatus: IAsyncStateContainer<string>;
|
||||
refreshProfile: () => void;
|
||||
submitEmployeeDetailsUpdate: (update: IEmployeeDetailsUpdate) => void;
|
||||
}
|
||||
|
||||
const ProfilePage: React.FC<IProfileParams> = (props) => {
|
||||
const [activeSheet, setActiveSheet] = useState<string>("profile");
|
||||
const settings = React.useContext(SettingsContext);
|
||||
const profile = props.profile;
|
||||
const name = profile.fullName || "Unknown";
|
||||
|
||||
return (
|
||||
<Row>
|
||||
<Col xs="3">
|
||||
<ListGroup>
|
||||
<ListGroupItem
|
||||
active={activeSheet === "profile"}
|
||||
tag="button"
|
||||
onClick={() => {
|
||||
setActiveSheet("profile");
|
||||
}}
|
||||
action
|
||||
>
|
||||
Profile
|
||||
</ListGroupItem>
|
||||
<ListGroupItem
|
||||
active={activeSheet === "eligibility"}
|
||||
tag="button"
|
||||
onClick={() => {
|
||||
setActiveSheet("eligibility");
|
||||
}}
|
||||
action
|
||||
>
|
||||
Payout Eligibility
|
||||
</ListGroupItem>
|
||||
<ListGroupItem
|
||||
active={activeSheet === "payouts"}
|
||||
tag="button"
|
||||
onClick={() => {
|
||||
setActiveSheet("payouts");
|
||||
}}
|
||||
action
|
||||
>
|
||||
Payouts
|
||||
</ListGroupItem>
|
||||
<PermissionGuard
|
||||
requiredPermissions={[
|
||||
"ASSIGN_ASSOCIATES_TO_DEPARTMENT"
|
||||
]}
|
||||
>
|
||||
<EmployeeStatusGuard employee={profile}>
|
||||
<ListGroupItem
|
||||
active={activeSheet === "departments"}
|
||||
tag="button"
|
||||
onClick={() => {
|
||||
setActiveSheet("departments");
|
||||
}}
|
||||
action
|
||||
>
|
||||
Manage Departments
|
||||
</ListGroupItem>
|
||||
</EmployeeStatusGuard>
|
||||
</PermissionGuard>
|
||||
<PermissionGuard
|
||||
requiredPermissions={[
|
||||
"ASSIGN_PERMISSIONS_TO_ASSOCIATES"
|
||||
]}
|
||||
>
|
||||
<EmployeeStatusGuard employee={profile}>
|
||||
<ListGroupItem
|
||||
active={activeSheet === "permissions"}
|
||||
tag="button"
|
||||
onClick={() => {
|
||||
setActiveSheet("permissions");
|
||||
}}
|
||||
action
|
||||
>
|
||||
Manage Permissions
|
||||
</ListGroupItem>
|
||||
</EmployeeStatusGuard>
|
||||
</PermissionGuard>
|
||||
<PermissionGuard
|
||||
requiredPermissions={["MANAGE_EMPLOYEE_DETAILS"]}
|
||||
>
|
||||
<EmployeeStatusGuard employee={profile}>
|
||||
<ListGroupItem
|
||||
active={activeSheet === "employeeDetails"}
|
||||
tag="button"
|
||||
onClick={() => {
|
||||
setActiveSheet("employeeDetails");
|
||||
}}
|
||||
action
|
||||
>
|
||||
Manage Employee Details
|
||||
</ListGroupItem>
|
||||
</EmployeeStatusGuard>
|
||||
</PermissionGuard>
|
||||
</ListGroup>
|
||||
<br />
|
||||
</Col>
|
||||
<Col xs="auto">
|
||||
<h3>{name}</h3>
|
||||
{profile.terminated ? (
|
||||
<Alert color="warning">
|
||||
Employee Not Active As Of{" "}
|
||||
{moment(profile.terminated).format("MM/DD/YYYY")}
|
||||
</Alert>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
{(() => {
|
||||
switch (activeSheet) {
|
||||
case "departments":
|
||||
return (
|
||||
<PermissionGuard
|
||||
requiredPermissions={[
|
||||
"ASSIGN_ASSOCIATES_TO_DEPARTMENT"
|
||||
]}
|
||||
>
|
||||
<EmployeePage
|
||||
employeeId={profile.employeeId}
|
||||
/>
|
||||
</PermissionGuard>
|
||||
);
|
||||
case "permissions":
|
||||
return (
|
||||
<PermissionGuard
|
||||
requiredPermissions={[
|
||||
"ASSIGN_PERMISSIONS_TO_ASSOCIATES"
|
||||
]}
|
||||
>
|
||||
<PermissionsPage
|
||||
additionalPermissions={
|
||||
profile.additionalPermissions ?? []
|
||||
}
|
||||
rolePermissions={
|
||||
profile.role.permissions
|
||||
}
|
||||
allPermissions={
|
||||
settings?.permissions ?? []
|
||||
}
|
||||
refreshProfile={() => {
|
||||
props.refreshProfile();
|
||||
}}
|
||||
employeeId={profile.employeeId}
|
||||
/>
|
||||
</PermissionGuard>
|
||||
);
|
||||
case "employeeDetails":
|
||||
return (
|
||||
<PermissionGuard
|
||||
requiredPermissions={[
|
||||
"MANAGE_EMPLOYEE_DETAILS"
|
||||
]}
|
||||
>
|
||||
<EmployeeDetailsPage
|
||||
profile={profile}
|
||||
employeeDetailsUpdateResult={
|
||||
props.employeeDetailsUpdateResult
|
||||
}
|
||||
submitStatus={props.submitStatus}
|
||||
refreshProfile={() => {
|
||||
props.refreshProfile();
|
||||
}}
|
||||
submitEmployeeDetailsUpdate={
|
||||
props.submitEmployeeDetailsUpdate
|
||||
}
|
||||
/>
|
||||
</PermissionGuard>
|
||||
);
|
||||
case "payouts":
|
||||
return (
|
||||
<PayoutPage employeeId={profile.employeeId} />
|
||||
);
|
||||
case "eligibility":
|
||||
return <EligiblePayouts profile={profile} />;
|
||||
case "profile":
|
||||
default:
|
||||
return <ProfileView profile={profile} />;
|
||||
}
|
||||
})()}
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfilePage;
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import React from "react";
|
||||
import { AutoTable } from "../../components/auto-table/AutoTable";
|
||||
import Translate from "./../../translate/Translate";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { IEmployeeSummary } from "../../shared/models/Employee";
|
||||
|
||||
export interface IProfileTableProps {
|
||||
profiles: IEmployeeSummary[];
|
||||
}
|
||||
|
||||
const ProfileResultsTable: React.FC<IProfileTableProps> = (props) => {
|
||||
const history = useHistory();
|
||||
|
||||
const headers = [
|
||||
Translate.Get.containers.profileResultsTable.headers.name,
|
||||
Translate.Get.containers.profileResultsTable.headers.id,
|
||||
Translate.Get.containers.profileResultsTable.headers.email
|
||||
];
|
||||
|
||||
const cells = [
|
||||
(x: IEmployeeSummary) => x.fullName,
|
||||
(x: IEmployeeSummary) => x.employeeId,
|
||||
(x: IEmployeeSummary) => x.adEmail
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="p-2">
|
||||
<h5>{Translate.Get.containers.profileResultsTable.title}</h5>
|
||||
<AutoTable
|
||||
className="border rounded"
|
||||
cells={cells}
|
||||
headers={headers}
|
||||
elements={props.profiles}
|
||||
onRowClick={(selection: IEmployeeSummary) => {
|
||||
history.push("/profile/" + selection.employeeId);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfileResultsTable;
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
import { mount } from "enzyme";
|
||||
import React from "react";
|
||||
import AsyncState from "../../shared/enumerations/AsyncState";
|
||||
import * as reactRedux from "react-redux";
|
||||
import { IProfileState } from "../../reducers/ProfileReducer";
|
||||
import ProfileSearchPage from "./ProfileSearchPage";
|
||||
|
||||
describe("A component for finding employee profiles", () => {
|
||||
const useSelectorMock = jest.spyOn(reactRedux, "useSelector");
|
||||
const useDispatchMock = jest.spyOn(reactRedux, "useDispatch");
|
||||
|
||||
beforeEach(() => {
|
||||
useSelectorMock.mockClear();
|
||||
useDispatchMock.mockClear();
|
||||
});
|
||||
|
||||
it("Displays a blank search form", () => {
|
||||
const profile: IProfileState = {
|
||||
profile: {
|
||||
data: {
|
||||
adEmail: "[email protected]",
|
||||
personId: 11,
|
||||
employeeId: "11771",
|
||||
homeDepartment: "HOME",
|
||||
baseRole: "QA",
|
||||
fullName: "John Tester",
|
||||
role: {
|
||||
name: "Role1",
|
||||
description: "NONE",
|
||||
permissions: [
|
||||
{ code: "VIEW_DASHBOARD", description: "" }
|
||||
],
|
||||
hasCommissions: true
|
||||
},
|
||||
additionalPermissions: [],
|
||||
departments: [],
|
||||
assignments: []
|
||||
},
|
||||
state: AsyncState.Success
|
||||
},
|
||||
searchResults: {
|
||||
state: AsyncState.Success,
|
||||
data: []
|
||||
}
|
||||
};
|
||||
|
||||
const dummyDispatch = jest.fn();
|
||||
useDispatchMock.mockReturnValue(dummyDispatch);
|
||||
useSelectorMock.mockReturnValue(profile);
|
||||
let wrapper = mount(<ProfileSearchPage />);
|
||||
|
||||
const found = wrapper.find("[data-test='profile-search-page']");
|
||||
expect(found.length).toBe(1);
|
||||
|
||||
expect(dummyDispatch).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { profileSearch } from "../../actions/ProfileActions";
|
||||
import { IAppState } from "../../store/Store";
|
||||
import { IProfileState } from "../../reducers/ProfileReducer";
|
||||
import AsyncState from "../../shared/enumerations/AsyncState";
|
||||
import Translate from "../../translate/Translate";
|
||||
import { FormGroup, Input, Label } from "reactstrap";
|
||||
import { debounce } from "debounce";
|
||||
import ProfileResultsTable from "./ProfileResultsTable";
|
||||
|
||||
const profileTranslate = Translate.Get.components.profileSearch;
|
||||
|
||||
const ProfileSearchPage: React.FC = () => {
|
||||
const [searchQuery, setSearchQuery] = useState<string>("");
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
if (searchQuery && searchQuery.length > 1) {
|
||||
search(searchQuery);
|
||||
}
|
||||
}, [searchQuery]);
|
||||
|
||||
const search = useCallback(
|
||||
debounce((query: string) => {
|
||||
dispatch(profileSearch(query));
|
||||
}, 500),
|
||||
[]
|
||||
);
|
||||
|
||||
const profileState = useSelector<IAppState, IProfileState>(
|
||||
(state) => state.profileState
|
||||
);
|
||||
const profileResults = profileState.searchResults;
|
||||
|
||||
return (
|
||||
<div data-test="profile-search-page">
|
||||
<FormGroup>
|
||||
<Label>{profileTranslate.title}</Label>
|
||||
<Input
|
||||
name="AccountNumber"
|
||||
bsSize="sm"
|
||||
value={searchQuery}
|
||||
onChange={(event) => {
|
||||
setSearchQuery(event.target.value);
|
||||
}}
|
||||
type="text"
|
||||
placeholder={profileTranslate.placeholder}
|
||||
/>
|
||||
</FormGroup>
|
||||
{profileResults.state === AsyncState.Success ? (
|
||||
<ProfileResultsTable profiles={profileResults?.data || []} />
|
||||
) : (
|
||||
<h4>{searchQuery ? profileTranslate.loading : ""}</h4>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfileSearchPage;
|
||||
@@ -0,0 +1,102 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import AsyncState from "../../shared/enumerations/AsyncState";
|
||||
import Translate from "../../translate/Translate";
|
||||
import { useParams } from "react-router-dom";
|
||||
import ProfilePage from "./ProfilePage";
|
||||
import IAsyncStateContainer from "../../shared/types/IAsyncStateContainer";
|
||||
import {
|
||||
IEmployee,
|
||||
IEmployeeDetailsUpdate,
|
||||
IEmployeeDetailsUpdateResult
|
||||
} from "../../shared/models/Employee";
|
||||
import ProfileDao from "../../shared/dao/ProfileDao";
|
||||
import { Alert } from "reactstrap";
|
||||
|
||||
interface IRouteMatchParams {
|
||||
employeeId?: string;
|
||||
}
|
||||
|
||||
const profileEditTranslate = Translate.Get.components.profileEdit;
|
||||
|
||||
const ProfileSelector: React.FC = () => {
|
||||
const [profile, setProfile] = useState<IAsyncStateContainer<IEmployee>>({
|
||||
state: AsyncState.Inactive
|
||||
});
|
||||
const [updateFeedback, setUpdateFeedback] = useState<string>("");
|
||||
const [submitStatus, setUpdatedSubmitStatus] = useState<
|
||||
IAsyncStateContainer<string>
|
||||
>({
|
||||
state: AsyncState.Inactive
|
||||
});
|
||||
|
||||
const { employeeId } = useParams<IRouteMatchParams>();
|
||||
|
||||
const refreshUserProfile = () => {
|
||||
ProfileDao.getProfile(employeeId).then(
|
||||
(result) => {
|
||||
setProfile(result);
|
||||
},
|
||||
(errorResult) => {
|
||||
setProfile(errorResult);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const submitCurrentEmployeeDetailsUpdate = async (
|
||||
update: IEmployeeDetailsUpdate
|
||||
) => {
|
||||
setUpdateFeedback(profileEditTranslate.loadingDetailUpdate);
|
||||
submitStatus.state = AsyncState.Loading;
|
||||
if (update) {
|
||||
ProfileDao.submitEmployeeDetailsUpdate(update).then((result) => {
|
||||
if (result.data) {
|
||||
setSubmitStatus(result.data);
|
||||
setUpdateFeedback(result.data.updateMessage);
|
||||
refreshUserProfile();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const setSubmitStatus = async (update: IEmployeeDetailsUpdateResult) => {
|
||||
if (update.updatedSuccessfully) {
|
||||
submitStatus.state = AsyncState.Success;
|
||||
} else {
|
||||
submitStatus.state = AsyncState.Failure;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
refreshUserProfile();
|
||||
}, []);
|
||||
|
||||
if (profile.state === AsyncState.Success && profile.data)
|
||||
return (
|
||||
<ProfilePage
|
||||
profile={profile.data}
|
||||
employeeDetailsUpdateResult={updateFeedback}
|
||||
submitStatus={submitStatus}
|
||||
refreshProfile={() => refreshUserProfile()}
|
||||
submitEmployeeDetailsUpdate={(
|
||||
employeeDetailsUpdate: IEmployeeDetailsUpdate
|
||||
) => submitCurrentEmployeeDetailsUpdate(employeeDetailsUpdate)}
|
||||
/>
|
||||
);
|
||||
|
||||
if (profile.state === AsyncState.Failure && profile.data)
|
||||
return (
|
||||
<ProfilePage
|
||||
profile={profile.data}
|
||||
employeeDetailsUpdateResult={updateFeedback}
|
||||
submitStatus={submitStatus}
|
||||
refreshProfile={() => refreshUserProfile()}
|
||||
submitEmployeeDetailsUpdate={(
|
||||
employeeDetailsUpdate: IEmployeeDetailsUpdate
|
||||
) => submitCurrentEmployeeDetailsUpdate(employeeDetailsUpdate)}
|
||||
/>
|
||||
);
|
||||
|
||||
return <h4>{profileEditTranslate.loading}</h4>;
|
||||
};
|
||||
|
||||
export default ProfileSelector;
|
||||
@@ -0,0 +1,17 @@
|
||||
## ProfileSearchPage
|
||||
|
||||
This component is used to search for a profile.
|
||||
|
||||
## ProfileResultsTable
|
||||
|
||||
This component is used to display employee search results.
|
||||
|
||||
## ProfileSelector
|
||||
|
||||
Responsible for choosing between the current appUser's
|
||||
profile and the optional route parameter if they were
|
||||
using the search or a deep link, then show that profile.
|
||||
|
||||
## ProfilePage
|
||||
|
||||
The actual profile management page.
|
||||
Reference in New Issue
Block a user