This commit is contained in:
Zeeshaun
2026-03-23 11:21:52 -05:00
parent de9ddd5001
commit a17bc4b4a9
426 changed files with 60574 additions and 1 deletions
@@ -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;
@@ -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;
@@ -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.