qpp-style 9.16.9 → 9.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.husky/pre-commit +5 -0
- package/components/Alert/Alert.stories.js +1 -1
- package/components/Alert/index.js +8 -1
- package/components/GovBanner/index.js +65 -57
- package/components/Header/HeaderContainer.jsx +1 -1
- package/components/Header/HeaderMenuItem.jsx +13 -6
- package/components/Modal/Modal.jsx +1 -1
- package/components/SideNav/AnimationGroup/AnimationGroup.jsx +2 -2
- package/components/SideNav/Content/LevelOneContent.jsx +121 -156
- package/components/SideNav/Content/SelectRole/index.js +79 -0
- package/components/SideNav/Content/SelectRole/utils.js +120 -0
- package/components/SideNav/Details/PracticeDetails.jsx +1 -1
- package/components/SideNav/Links/NavLinkDrawer.jsx +1 -1
- package/components/SideNav/Links/NavLinkInline.jsx +6 -1
- package/components/SideNav/UI/SideNavUI.jsx +7 -3
- package/components/SideNav/helpers.js +213 -0
- package/components/Tabs/Tab.js +1 -1
- package/components/Tabs/TabPanel.js +8 -1
- package/components/Tabs/index.js +46 -41
- package/components/TextInput/index.js +1 -1
- package/components/hooks/useGetConfig.js +1 -0
- package/dist/browser.js +1 -1
- package/dist/browser.js.LICENSE.txt +10 -1
- package/dist/browser.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.LICENSE.txt +10 -1
- package/dist/index.js.map +1 -1
- package/dist/react/index.js +1 -1
- package/dist/react/index.js.LICENSE.txt +9 -0
- package/dist/react/index.js.map +1 -1
- package/images/icons/svg/arrow-down.svg +3 -1
- package/images/icons/svg/arrow-up.svg +3 -0
- package/lib/SvgComponents.jsx +15 -0
- package/package.json +10 -8
- package/storybook-static/favicon.ico +0 -0
- package/styles/qppds/components/_dropdown.scss +1 -0
- package/styles/qppds/components/_sidebar.scss +1 -0
- package/styles/qppds/components/sidebar/_links.scss +14 -1
- package/styles/qppds/components/sidebar/_select-role-dropdown.scss +21 -0
- package/styles/qppds/components/sidebar/_select-role.scss +13 -0
- package/coverage/clover.xml +0 -1294
- package/coverage/coverage-final.json +0 -70
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +0 -461
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/react/components/Accordion/index.html +0 -116
- package/coverage/lcov-report/react/components/Accordion/index.jsx.html +0 -364
- package/coverage/lcov-report/react/components/Button/index.html +0 -116
- package/coverage/lcov-report/react/components/Button/index.js.html +0 -355
- package/coverage/lcov-report/react/components/Error/Collapsible.jsx.html +0 -349
- package/coverage/lcov-report/react/components/Error/ErrorUI.jsx.html +0 -178
- package/coverage/lcov-report/react/components/Error/error.js.html +0 -163
- package/coverage/lcov-report/react/components/Error/index.html +0 -146
- package/coverage/lcov-report/react/components/Footer/FooterUI.jsx.html +0 -880
- package/coverage/lcov-report/react/components/Footer/LegacyFooterUI.jsx.html +0 -667
- package/coverage/lcov-report/react/components/Footer/SocialLinks.jsx.html +0 -265
- package/coverage/lcov-report/react/components/Footer/Subscribe.jsx.html +0 -187
- package/coverage/lcov-report/react/components/Footer/footer.js.html +0 -175
- package/coverage/lcov-report/react/components/Footer/index.html +0 -176
- package/coverage/lcov-report/react/components/GovBanner/index.html +0 -116
- package/coverage/lcov-report/react/components/GovBanner/index.js.html +0 -409
- package/coverage/lcov-report/react/components/Header/HeaderAccountMenu.jsx.html +0 -592
- package/coverage/lcov-report/react/components/Header/HeaderCancel.jsx.html +0 -133
- package/coverage/lcov-report/react/components/Header/HeaderContainer.jsx.html +0 -280
- package/coverage/lcov-report/react/components/Header/HeaderLogo.jsx.html +0 -211
- package/coverage/lcov-report/react/components/Header/HeaderMenuButton.js.html +0 -235
- package/coverage/lcov-report/react/components/Header/HeaderMenuItem.jsx.html +0 -781
- package/coverage/lcov-report/react/components/Header/HeaderMenuLink.js.html +0 -235
- package/coverage/lcov-report/react/components/Header/HeaderMenuSignOutButton.js.html +0 -271
- package/coverage/lcov-report/react/components/Header/HeaderMobileButton.js.html +0 -196
- package/coverage/lcov-report/react/components/Header/HeaderUI.jsx.html +0 -586
- package/coverage/lcov-report/react/components/Header/HelpIcon.jsx.html +0 -181
- package/coverage/lcov-report/react/components/Header/ImpersonatorBanner.jsx.html +0 -331
- package/coverage/lcov-report/react/components/Header/NavigationButtonIcon.jsx.html +0 -166
- package/coverage/lcov-report/react/components/Header/header.js.html +0 -205
- package/coverage/lcov-report/react/components/Header/hooks.js.html +0 -241
- package/coverage/lcov-report/react/components/Header/index.html +0 -341
- package/coverage/lcov-report/react/components/Header/utag-helpers.js.html +0 -112
- package/coverage/lcov-report/react/components/Infotip/Infotip.jsx.html +0 -310
- package/coverage/lcov-report/react/components/Infotip/InfotipIcon.jsx.html +0 -208
- package/coverage/lcov-report/react/components/Infotip/index.html +0 -146
- package/coverage/lcov-report/react/components/Infotip/index.js.html +0 -94
- package/coverage/lcov-report/react/components/Modal/LegacyModal.jsx.html +0 -301
- package/coverage/lcov-report/react/components/Modal/Modal.jsx.html +0 -505
- package/coverage/lcov-report/react/components/Modal/index.html +0 -146
- package/coverage/lcov-report/react/components/Modal/index.jsx.html +0 -151
- package/coverage/lcov-report/react/components/NotificationBanner/index.html +0 -116
- package/coverage/lcov-report/react/components/NotificationBanner/index.js.html +0 -880
- package/coverage/lcov-report/react/components/SanitizedContent/index.html +0 -116
- package/coverage/lcov-report/react/components/SanitizedContent/index.jsx.html +0 -685
- package/coverage/lcov-report/react/components/SessionDialog/index.html +0 -116
- package/coverage/lcov-report/react/components/SessionDialog/sessionDialog.js.html +0 -163
- package/coverage/lcov-report/react/components/SessionDialogUI.jsx.html +0 -868
- package/coverage/lcov-report/react/components/SideNav/AnimationGroup/AnimationGroup.jsx.html +0 -166
- package/coverage/lcov-report/react/components/SideNav/AnimationGroup/index.html +0 -116
- package/coverage/lcov-report/react/components/SideNav/Chart/ScoreChart.jsx.html +0 -889
- package/coverage/lcov-report/react/components/SideNav/Chart/index.html +0 -131
- package/coverage/lcov-report/react/components/SideNav/Chart/index.js.html +0 -94
- package/coverage/lcov-report/react/components/SideNav/Content/LevelOneContent.jsx.html +0 -709
- package/coverage/lcov-report/react/components/SideNav/Content/LevelTwoContent.jsx.html +0 -733
- package/coverage/lcov-report/react/components/SideNav/Content/index.html +0 -146
- package/coverage/lcov-report/react/components/SideNav/Content/index.js.html +0 -97
- package/coverage/lcov-report/react/components/SideNav/Details/IndividualDetails.jsx.html +0 -139
- package/coverage/lcov-report/react/components/SideNav/Details/PracticeDetails.jsx.html +0 -220
- package/coverage/lcov-report/react/components/SideNav/Details/index.html +0 -146
- package/coverage/lcov-report/react/components/SideNav/Details/index.js.html +0 -97
- package/coverage/lcov-report/react/components/SideNav/Links/CmsSwitchLink.jsx.html +0 -208
- package/coverage/lcov-report/react/components/SideNav/Links/NavItemInline.jsx.html +0 -247
- package/coverage/lcov-report/react/components/SideNav/Links/NavLinkContainer.jsx.html +0 -199
- package/coverage/lcov-report/react/components/SideNav/Links/NavLinkDrawer.jsx.html +0 -883
- package/coverage/lcov-report/react/components/SideNav/Links/NavLinkInline.jsx.html +0 -406
- package/coverage/lcov-report/react/components/SideNav/Links/NavLinkToggle.jsx.html +0 -187
- package/coverage/lcov-report/react/components/SideNav/Links/index.html +0 -206
- package/coverage/lcov-report/react/components/SideNav/Links/index.js.html +0 -124
- package/coverage/lcov-report/react/components/SideNav/UI/SideNavUI.jsx.html +0 -1087
- package/coverage/lcov-report/react/components/SideNav/UI/index.html +0 -131
- package/coverage/lcov-report/react/components/SideNav/UI/index.js.html +0 -94
- package/coverage/lcov-report/react/components/SideNav/helpers.js.html +0 -238
- package/coverage/lcov-report/react/components/SideNav/index.html +0 -131
- package/coverage/lcov-report/react/components/SideNav/index.js.html +0 -244
- package/coverage/lcov-report/react/components/Tooltip/Tooltip.jsx.html +0 -349
- package/coverage/lcov-report/react/components/Tooltip/index.html +0 -146
- package/coverage/lcov-report/react/components/Tooltip/index.js.html +0 -94
- package/coverage/lcov-report/react/components/Tooltip/position.js.html +0 -289
- package/coverage/lcov-report/react/components/hooks/index.html +0 -116
- package/coverage/lcov-report/react/components/hooks/useGetConfig.js.html +0 -307
- package/coverage/lcov-report/react/components/index.html +0 -116
- package/coverage/lcov-report/react/index.html +0 -116
- package/coverage/lcov-report/react/index.js.html +0 -178
- package/coverage/lcov-report/react/lib/Chevron.jsx.html +0 -181
- package/coverage/lcov-report/react/lib/SvgComponents.jsx.html +0 -1702
- package/coverage/lcov-report/react/lib/index.html +0 -146
- package/coverage/lcov-report/react/lib/svg-definitions.svg.html +0 -460
- package/coverage/lcov-report/react/session/index.html +0 -161
- package/coverage/lcov-report/react/session/index.js.html +0 -100
- package/coverage/lcov-report/react/session/logout.js.html +0 -307
- package/coverage/lcov-report/react/session/refresh.js.html +0 -232
- package/coverage/lcov-report/react/session/ttl.js.html +0 -196
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -196
- package/coverage/lcov.info +0 -2924
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import React, { useEffect } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import cookie from 'cookie';
|
|
4
|
+
import Dropdown from '../../../Dropdown';
|
|
5
|
+
import {
|
|
6
|
+
loadRoleOptions,
|
|
7
|
+
redirectPage,
|
|
8
|
+
getLocalStorageRoleState,
|
|
9
|
+
initializeLocalStorageRoleState,
|
|
10
|
+
updateLocalStorageRoleState,
|
|
11
|
+
} from './utils';
|
|
12
|
+
|
|
13
|
+
const SelectRole = ({ selectedRole, setSelectedRole }) => {
|
|
14
|
+
const {
|
|
15
|
+
qpp_has_authorizations: hasAuthorizations,
|
|
16
|
+
qpp_cms_internal_authorized: cmsInternalRoleValues,
|
|
17
|
+
qpp_impersonated_user: isHelpdeskRoleAndImpersonating,
|
|
18
|
+
} = cookie.parse(document.cookie);
|
|
19
|
+
|
|
20
|
+
const ROLE_OPTIONS = loadRoleOptions([
|
|
21
|
+
hasAuthorizations,
|
|
22
|
+
cmsInternalRoleValues,
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
const handleSelection = (e) => {
|
|
26
|
+
e.preventDefault();
|
|
27
|
+
const dropdownValue = e.target.value;
|
|
28
|
+
const newRoleSelected = ROLE_OPTIONS.filter(
|
|
29
|
+
(opt) => opt.value === dropdownValue
|
|
30
|
+
)[0].value;
|
|
31
|
+
// Update localStorage with the current selected role in state & the new role selected
|
|
32
|
+
updateLocalStorageRoleState(selectedRole, newRoleSelected);
|
|
33
|
+
setSelectedRole(newRoleSelected);
|
|
34
|
+
redirectPage(newRoleSelected);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Set role on init render
|
|
38
|
+
// below is executed on every page refresh including on side nav link navigation & on role selection
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
// Check localstorage & set initial selected role
|
|
41
|
+
const { selectedRole, previousSelectedRole } = getLocalStorageRoleState();
|
|
42
|
+
const roleValue = selectedRole || ROLE_OPTIONS[0].value;
|
|
43
|
+
|
|
44
|
+
// Set localStorage values if not set
|
|
45
|
+
initializeLocalStorageRoleState(roleValue);
|
|
46
|
+
|
|
47
|
+
// Update state
|
|
48
|
+
setSelectedRole(roleValue);
|
|
49
|
+
|
|
50
|
+
// No previous role set && current role is QPP User in localStorage values indicates
|
|
51
|
+
// initial render, where a user has NOT used the dropdown to change the user role.
|
|
52
|
+
// If the above condition is not met, redirect to default page of selected role
|
|
53
|
+
if (!previousSelectedRole && selectedRole !== 'QPP User') {
|
|
54
|
+
redirectPage(roleValue);
|
|
55
|
+
}
|
|
56
|
+
}, []);
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<div className="SelectRole__Container">
|
|
60
|
+
<p className="SelectRole__Text">You are viewing as a</p>
|
|
61
|
+
<Dropdown
|
|
62
|
+
value={selectedRole}
|
|
63
|
+
name="user-role-selection"
|
|
64
|
+
ariaLabel="Select User Role"
|
|
65
|
+
className="qpp-u-width--100 SelectRole__Dropdown"
|
|
66
|
+
options={ROLE_OPTIONS}
|
|
67
|
+
onChange={handleSelection}
|
|
68
|
+
disabled={isHelpdeskRoleAndImpersonating}
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
SelectRole.propTypes = {
|
|
75
|
+
selectedRole: PropTypes.string,
|
|
76
|
+
setSelectedRole: PropTypes.func,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export default SelectRole;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract values from cookies, return parsed values in an array
|
|
3
|
+
* @param {array} cookies
|
|
4
|
+
* @returns {array}
|
|
5
|
+
*/
|
|
6
|
+
const extractCookieValues = (cookies) => {
|
|
7
|
+
const [hasAuthorizations, cmsInternalRoleValues] = cookies;
|
|
8
|
+
const cmsInternalRoles = cmsInternalRoleValues
|
|
9
|
+
? JSON.parse(cmsInternalRoleValues)
|
|
10
|
+
: null;
|
|
11
|
+
return [hasAuthorizations, cmsInternalRoles];
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Check if internal cms roles has Reviewer role
|
|
16
|
+
* @param {array} cmsInternalRoles array of values
|
|
17
|
+
* @returns {boolean}
|
|
18
|
+
*/
|
|
19
|
+
const hasReviewerRole = (cmsInternalRoles) => {
|
|
20
|
+
if (!cmsInternalRoles) return false;
|
|
21
|
+
return cmsInternalRoles.some(
|
|
22
|
+
(name) =>
|
|
23
|
+
name === 'QPP Self-Nomination' ||
|
|
24
|
+
name === 'QPP Targeted Review & Exceptions'
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Check if internal cms roles includes Helpdek role
|
|
30
|
+
* @param {array} cmsInternalRoles array of values
|
|
31
|
+
* @returns {boolean}
|
|
32
|
+
*/
|
|
33
|
+
const hasHelpdeskRole = (cmsInternalRoles) => {
|
|
34
|
+
if (!cmsInternalRoles) return false;
|
|
35
|
+
return cmsInternalRoles.includes('QPP Helpdesk');
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Transforms cookie values into optios format for use in role seelction dropdown
|
|
40
|
+
* @param {array} values extracted & parsed vookie values
|
|
41
|
+
* @returns {array}
|
|
42
|
+
*/
|
|
43
|
+
const transformCookieValues = (values) => {
|
|
44
|
+
const optionValues = [];
|
|
45
|
+
const [hasAuthorizations, cmsInternalRoles] = values;
|
|
46
|
+
|
|
47
|
+
if (hasAuthorizations === 'true') {
|
|
48
|
+
optionValues.push({ name: 'QPP User', value: 'QPP User' });
|
|
49
|
+
}
|
|
50
|
+
if (hasReviewerRole(cmsInternalRoles)) {
|
|
51
|
+
optionValues.push({ name: 'Reviewer', value: 'Reviewer' });
|
|
52
|
+
}
|
|
53
|
+
if (hasHelpdeskRole(cmsInternalRoles)) {
|
|
54
|
+
optionValues.push({ name: 'Helpdesk Viewer', value: 'Helpdesk Viewer' });
|
|
55
|
+
}
|
|
56
|
+
return optionValues;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
*
|
|
61
|
+
* @param {array} cookies authz & cms_internal_authz values parsed from cookies
|
|
62
|
+
* @returns {array} of objects for use in role dropdown
|
|
63
|
+
*/
|
|
64
|
+
export const loadRoleOptions = (cookies) => {
|
|
65
|
+
const cookieValues = extractCookieValues(cookies);
|
|
66
|
+
return transformCookieValues(cookieValues);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const defaultPageByRole = {
|
|
70
|
+
'QPP User': '/user/submissions',
|
|
71
|
+
'Helpdesk Viewer': '/user/helpdesk-viewing-tool',
|
|
72
|
+
Reviewer: '/user/reviewers',
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get redirect url/page by role
|
|
77
|
+
* @param {string} role
|
|
78
|
+
* @returns {string}
|
|
79
|
+
*/
|
|
80
|
+
export const redirectPage = (role) => {
|
|
81
|
+
const defaultPage = defaultPageByRole[role];
|
|
82
|
+
const { pathname } = window.location;
|
|
83
|
+
if (pathname === defaultPage) return;
|
|
84
|
+
else window.location.assign(defaultPage);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get localStorage values related to user role
|
|
89
|
+
* @returns {object}
|
|
90
|
+
*/
|
|
91
|
+
export const getLocalStorageRoleState = () => ({
|
|
92
|
+
selectedRole: localStorage.getItem('selectedRole'),
|
|
93
|
+
previousSelectedRole: localStorage.getItem('previousSelectedRole'),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Initialize role values in localStorage
|
|
98
|
+
* @param {string} roleValue selector role value from dropdown
|
|
99
|
+
* @returns {void}
|
|
100
|
+
*/
|
|
101
|
+
export const initializeLocalStorageRoleState = (roleValue) => {
|
|
102
|
+
const localStorageValues = getLocalStorageRoleState();
|
|
103
|
+
|
|
104
|
+
if (!localStorageValues.selectedRole) {
|
|
105
|
+
localStorage.setItem('selectedRole', roleValue);
|
|
106
|
+
}
|
|
107
|
+
if (!localStorageValues.previousSelectedRole) {
|
|
108
|
+
localStorage.setItem('previousSelectedRole', '');
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Update role values in localStorage
|
|
114
|
+
* @param {string} currentRole current/existing user role
|
|
115
|
+
* @param {string} updatedRole new user role selected from dropdown
|
|
116
|
+
*/
|
|
117
|
+
export const updateLocalStorageRoleState = (currentRole, updatedRole) => {
|
|
118
|
+
localStorage.setItem('previousSelectedRole', currentRole);
|
|
119
|
+
localStorage.setItem('selectedRole', updatedRole);
|
|
120
|
+
};
|
|
@@ -216,7 +216,7 @@ const NavLinkDrawer = ({
|
|
|
216
216
|
<div className={isExpanded ? 'link-body' : 'link-body collapsed'}>
|
|
217
217
|
{renderLeftIcon(leftIcon)}
|
|
218
218
|
<AnimationGroup display={isExpanded}>
|
|
219
|
-
<span>{label}</span>
|
|
219
|
+
<span className="sidebar-inline-link-label">{label}</span>
|
|
220
220
|
</AnimationGroup>
|
|
221
221
|
</div>
|
|
222
222
|
{isExpanded && !isAlwaysOpen && (
|
|
@@ -8,6 +8,7 @@ const NavLinkInline = ({
|
|
|
8
8
|
urlExpressionToMatch,
|
|
9
9
|
className,
|
|
10
10
|
icon,
|
|
11
|
+
rightIcon,
|
|
11
12
|
label,
|
|
12
13
|
isActive,
|
|
13
14
|
showLabel,
|
|
@@ -78,7 +79,10 @@ const NavLinkInline = ({
|
|
|
78
79
|
{icon}
|
|
79
80
|
</svg>
|
|
80
81
|
<AnimationGroup display={showLabel}>
|
|
81
|
-
<span tabIndex="-1"
|
|
82
|
+
<span tabIndex="-1" className="sidebar-inline-link-label">
|
|
83
|
+
{label}
|
|
84
|
+
{rightIcon}
|
|
85
|
+
</span>
|
|
82
86
|
</AnimationGroup>
|
|
83
87
|
</a>
|
|
84
88
|
{showTooltip && (
|
|
@@ -95,6 +99,7 @@ NavLinkInline.propTypes = {
|
|
|
95
99
|
url: PropTypes.string,
|
|
96
100
|
className: PropTypes.string,
|
|
97
101
|
icon: PropTypes.element,
|
|
102
|
+
rightIcon: PropTypes.element,
|
|
98
103
|
label: PropTypes.string,
|
|
99
104
|
isActive: PropTypes.bool,
|
|
100
105
|
showLabel: PropTypes.bool,
|
|
@@ -91,15 +91,19 @@ const SideNavUI = ({
|
|
|
91
91
|
|
|
92
92
|
const hasDarkerBackground = (darkerBackground) => {
|
|
93
93
|
return darkerBackground ? 'hr-smaller-vertical-spacing' : '';
|
|
94
|
-
}
|
|
94
|
+
};
|
|
95
95
|
|
|
96
96
|
const itemsMarkup = (contentItems || []).map((item, index) => {
|
|
97
97
|
const key = `${JSON.stringify(item)}-${index}-${containerRecursionId}`;
|
|
98
98
|
return (
|
|
99
99
|
{
|
|
100
100
|
divider: (
|
|
101
|
-
<AnimationGroup
|
|
102
|
-
|
|
101
|
+
<AnimationGroup
|
|
102
|
+
display={isExpandedState}
|
|
103
|
+
key={key}
|
|
104
|
+
darkerBackground={item.darkerBackground}
|
|
105
|
+
>
|
|
106
|
+
<hr className={`${hasDarkerBackground(item.darkerBackground)}`} />
|
|
103
107
|
</AnimationGroup>
|
|
104
108
|
),
|
|
105
109
|
container: getDynamicContent(
|
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
import cookie from 'cookie';
|
|
2
|
+
import {
|
|
3
|
+
AccountHomeIcon,
|
|
4
|
+
DashboardIcon,
|
|
5
|
+
FacilityBasedPreviewIcon,
|
|
6
|
+
HardshipIcon,
|
|
7
|
+
HelpSupportIcon,
|
|
8
|
+
ManageUsersIcon,
|
|
9
|
+
MyApplicationsIcon,
|
|
10
|
+
MyTestDataIcon,
|
|
11
|
+
PaymentIcon,
|
|
12
|
+
PhysicianCompareIcon,
|
|
13
|
+
StarIcon,
|
|
14
|
+
TargetIcon,
|
|
15
|
+
IndividualReporting,
|
|
16
|
+
} from '../../lib/SvgComponents';
|
|
17
|
+
|
|
1
18
|
const submissionsUrl = '/user/submissions';
|
|
2
19
|
const dashboardUrl = '/user/dashboard';
|
|
3
20
|
const manageUrl = '/user/manage-access';
|
|
@@ -36,6 +53,195 @@ const handleNavigation = (e, linkCallbackFunction, label) => {
|
|
|
36
53
|
});
|
|
37
54
|
};
|
|
38
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Load icon by url
|
|
58
|
+
* @param {string} url
|
|
59
|
+
* @returns svg icon
|
|
60
|
+
*/
|
|
61
|
+
const getIcon = (url) => {
|
|
62
|
+
const icons = {
|
|
63
|
+
[dashboardUrl]: DashboardIcon,
|
|
64
|
+
[feedbackUrl]: StarIcon,
|
|
65
|
+
[manageUrl]: ManageUsersIcon,
|
|
66
|
+
[physicianCompareUrl]: PhysicianCompareIcon,
|
|
67
|
+
[reportsPortalUrl]: HardshipIcon,
|
|
68
|
+
[submissionsUrl]: AccountHomeIcon,
|
|
69
|
+
[facilityBasedPreviewBaseUrl]: FacilityBasedPreviewIcon,
|
|
70
|
+
'/developers': HelpSupportIcon,
|
|
71
|
+
'/resources/help-and-support': HelpSupportIcon,
|
|
72
|
+
'/user/apm-incentive-payments': PaymentIcon,
|
|
73
|
+
'/user/applications': MyApplicationsIcon,
|
|
74
|
+
'/user/exception/#/landing': HardshipIcon,
|
|
75
|
+
'/user/targeted-review/#/landing': TargetIcon,
|
|
76
|
+
'/user/test-data': MyTestDataIcon,
|
|
77
|
+
'/user/self-nomination/#/landing': IndividualReporting,
|
|
78
|
+
'/user/reviewers': AccountHomeIcon,
|
|
79
|
+
'/reviewer/exception': HardshipIcon,
|
|
80
|
+
'/reviewer/targeted-review': TargetIcon,
|
|
81
|
+
'/self-nomination': IndividualReporting,
|
|
82
|
+
};
|
|
83
|
+
return icons[url];
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Check if Reviewer or Self-Nom role
|
|
88
|
+
* @param {string} name
|
|
89
|
+
* @returns {boolean}
|
|
90
|
+
*/
|
|
91
|
+
const isReviewerOrSelfNomRole = (name) =>
|
|
92
|
+
name === 'QPP Self-Nomination' || name === 'QPP Targeted Review & Exceptions';
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Check if user is currently impersonating
|
|
96
|
+
* @param {string} docCookie document cookie
|
|
97
|
+
* @returns {boolean}
|
|
98
|
+
*/
|
|
99
|
+
const isImpersonating = (docCookie) => {
|
|
100
|
+
const parsedCookies = cookie.parse(docCookie);
|
|
101
|
+
return (
|
|
102
|
+
parsedCookies.qpp_can_impersonate && parsedCookies.qpp_impersonated_user
|
|
103
|
+
);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Check if user has multiple roles, used to display Select Role dropdown
|
|
108
|
+
* @param {string} docCookie document cookie
|
|
109
|
+
* @returns {boolean}
|
|
110
|
+
*/
|
|
111
|
+
const isMultiRoleUser = (docCookie) => {
|
|
112
|
+
const { qpp_cms_internal_authorized } = cookie.parse(docCookie);
|
|
113
|
+
let internalAuthz = [];
|
|
114
|
+
if (qpp_cms_internal_authorized) {
|
|
115
|
+
internalAuthz = JSON.parse(qpp_cms_internal_authorized);
|
|
116
|
+
}
|
|
117
|
+
// Filter out test internalCms authz
|
|
118
|
+
const filteredInternalAuthz = internalAuthz.filter((internalAuth) => {
|
|
119
|
+
return (
|
|
120
|
+
internalAuth !== 'Test internalCms Resource' &&
|
|
121
|
+
internalAuth !== 'Test internalCms Resource 2'
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
return filteredInternalAuthz.length > 1;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Transform cookie values to permissions object
|
|
129
|
+
* @param {object} cookies parsed document.cookie values
|
|
130
|
+
* @returns {object}
|
|
131
|
+
*/
|
|
132
|
+
const transformCookieValues = (cookies) => {
|
|
133
|
+
const {
|
|
134
|
+
qpp_can_impersonate,
|
|
135
|
+
qpp_has_authorizations,
|
|
136
|
+
user_has_apm_payments,
|
|
137
|
+
qpp_cms_internal_authorized,
|
|
138
|
+
qpp_ehr_authorized,
|
|
139
|
+
qpp_has_non_registry_authorizations,
|
|
140
|
+
} = cookies;
|
|
141
|
+
return {
|
|
142
|
+
canImpersonate: qpp_can_impersonate === 'true',
|
|
143
|
+
hasAuthorizations: qpp_has_authorizations === 'true',
|
|
144
|
+
hasApmPayments: user_has_apm_payments === 'true',
|
|
145
|
+
internalReviewerNames: qpp_cms_internal_authorized
|
|
146
|
+
? JSON.parse(qpp_cms_internal_authorized)
|
|
147
|
+
: [],
|
|
148
|
+
ehrAuthorized: qpp_ehr_authorized === 'true',
|
|
149
|
+
hasNonRegistryAuthorizations:
|
|
150
|
+
qpp_has_non_registry_authorizations === 'true',
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const permissionsByRole = {
|
|
155
|
+
'QPP User': {
|
|
156
|
+
canImpersonate: false,
|
|
157
|
+
hasAuthorizations: true,
|
|
158
|
+
hasApmPayments: true,
|
|
159
|
+
},
|
|
160
|
+
Reviewer: {
|
|
161
|
+
hasAuthorizations: false,
|
|
162
|
+
hasApmPaymentss: false,
|
|
163
|
+
},
|
|
164
|
+
'Helpdesk Viewer': {
|
|
165
|
+
hasAuthorizations: false,
|
|
166
|
+
hasApmPayments: false,
|
|
167
|
+
hasNonRegistryAuthorizations: false,
|
|
168
|
+
internalReviewerNames: ['QPP Helpdesk'],
|
|
169
|
+
},
|
|
170
|
+
// Helpdesk role && impersonating (use impersonated user authz values)
|
|
171
|
+
Impersonation: {
|
|
172
|
+
internalReviewerNames: ['QPP Helpdesk'],
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Return permissions by role
|
|
178
|
+
* @param {string} selectedRole selected user role
|
|
179
|
+
* @param {object} permissionValues default permission values from transformed parsed cookie
|
|
180
|
+
* @returns {object}
|
|
181
|
+
*/
|
|
182
|
+
const loadUserPermissionsByRole = (selectedRole, permissionValues) => {
|
|
183
|
+
return {
|
|
184
|
+
...permissionValues,
|
|
185
|
+
...permissionsByRole[selectedRole],
|
|
186
|
+
};
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Helper fn to return user permissions/properties from cookies set on login
|
|
191
|
+
* @param {string} docCookie cookie values set on login
|
|
192
|
+
* @param {string} selectedRole string of selected role if user has multiple roles
|
|
193
|
+
* @returns {object}
|
|
194
|
+
*/
|
|
195
|
+
const loadUserPermissions = (docCookie, selectedRole) => {
|
|
196
|
+
const parsedCookies = cookie.parse(docCookie);
|
|
197
|
+
const permissionValues = transformCookieValues(parsedCookies);
|
|
198
|
+
const impersonating = isImpersonating(docCookie);
|
|
199
|
+
const role = impersonating ? 'Impersonation' : selectedRole;
|
|
200
|
+
|
|
201
|
+
if (role) {
|
|
202
|
+
return loadUserPermissionsByRole(role, permissionValues);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return permissionValues;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Return map of link conditionals based on user cookies for side nav
|
|
210
|
+
* @param {object} userPermissions user permissions/properties derived from cookies set on login
|
|
211
|
+
* @returns {object}
|
|
212
|
+
*/
|
|
213
|
+
const getUrlConditionMap = (userPermissions) => {
|
|
214
|
+
return {
|
|
215
|
+
[dashboardUrl]: userPermissions.hasAuthorizations,
|
|
216
|
+
[feedbackUrl]: userPermissions.hasAuthorizations,
|
|
217
|
+
'/user/apm-incentive-payments': userPermissions.hasApmPayments,
|
|
218
|
+
[physicianCompareUrl]: userPermissions.hasNonRegistryAuthorizations,
|
|
219
|
+
[reportsPortalUrl]: userPermissions.hasAuthorizations,
|
|
220
|
+
[facilityBasedPreviewBaseUrl]: userPermissions.hasAuthorizations,
|
|
221
|
+
'/user/helpdesk-viewing-tool': userPermissions.canImpersonate,
|
|
222
|
+
'/reviewer/exception': userPermissions.internalReviewerNames?.includes(
|
|
223
|
+
'QPP Targeted Review & Exceptions'
|
|
224
|
+
),
|
|
225
|
+
'/reviewer/targeted-review':
|
|
226
|
+
userPermissions.internalReviewerNames?.includes(
|
|
227
|
+
'QPP Targeted Review & Exceptions'
|
|
228
|
+
),
|
|
229
|
+
'/self-nomination': userPermissions.internalReviewerNames?.includes(
|
|
230
|
+
'QPP Self-Nomination'
|
|
231
|
+
),
|
|
232
|
+
|
|
233
|
+
// dev pre
|
|
234
|
+
'/user/applications': userPermissions.ehrAuthorized,
|
|
235
|
+
'/user/test-data': userPermissions.ehrAuthorized,
|
|
236
|
+
};
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const isImpersonationLink = (docCookie, linkLabel) => {
|
|
240
|
+
if (!isImpersonating(docCookie)) return;
|
|
241
|
+
const helpdeskLinks = ['Account Home', 'Manage Access', 'Help and Support'];
|
|
242
|
+
return !helpdeskLinks.includes(linkLabel);
|
|
243
|
+
};
|
|
244
|
+
|
|
39
245
|
module.exports = {
|
|
40
246
|
submissionsUrl,
|
|
41
247
|
dashboardUrl,
|
|
@@ -48,4 +254,11 @@ module.exports = {
|
|
|
48
254
|
reportsPortalUrl,
|
|
49
255
|
facilityBasedPreviewBaseUrl,
|
|
50
256
|
viewingToolUrl,
|
|
257
|
+
loadUserPermissions,
|
|
258
|
+
getUrlConditionMap,
|
|
259
|
+
isMultiRoleUser,
|
|
260
|
+
isReviewerOrSelfNomRole,
|
|
261
|
+
getIcon,
|
|
262
|
+
isImpersonating,
|
|
263
|
+
isImpersonationLink,
|
|
51
264
|
};
|
package/components/Tabs/Tab.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
3
|
|
|
4
|
-
export const TabPanel = ({
|
|
4
|
+
export const TabPanel = ({
|
|
5
|
+
children,
|
|
6
|
+
value,
|
|
7
|
+
id,
|
|
8
|
+
index,
|
|
9
|
+
className,
|
|
10
|
+
disabled,
|
|
11
|
+
}) => {
|
|
5
12
|
const classes = ['qpp-c-tabs__panel', className].filter(Boolean).join(' ');
|
|
6
13
|
return (
|
|
7
14
|
<div
|
package/components/Tabs/index.js
CHANGED
|
@@ -1,61 +1,69 @@
|
|
|
1
|
-
import React, { useEffect, useState } from 'react'
|
|
2
|
-
import PropTypes from 'prop-types'
|
|
3
|
-
import MuiTabs from '@material-ui/core/Tabs'
|
|
4
|
-
import TabPanel from './TabPanel'
|
|
5
|
-
import LinkTab from './Tab'
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import MuiTabs from '@material-ui/core/Tabs';
|
|
4
|
+
import TabPanel from './TabPanel';
|
|
5
|
+
import LinkTab from './Tab';
|
|
6
6
|
|
|
7
|
-
const isTabPanel = (child) => child?.type === TabPanel
|
|
7
|
+
const isTabPanel = (child) => child?.type === TabPanel;
|
|
8
8
|
|
|
9
9
|
const getPanelChildren = (children) => {
|
|
10
|
-
return React.Children.toArray(children).filter(isTabPanel)
|
|
10
|
+
return React.Children.toArray(children).filter(isTabPanel);
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
const Tabs = ({
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
const Tabs = ({
|
|
14
|
+
children,
|
|
15
|
+
onChange,
|
|
16
|
+
selected,
|
|
17
|
+
defaultSelectedId,
|
|
18
|
+
ariaLabel,
|
|
19
|
+
}) => {
|
|
20
|
+
const [value, setValue] = useState(defaultSelectedId || selected);
|
|
21
|
+
|
|
16
22
|
useEffect(() => {
|
|
17
23
|
if (selected) {
|
|
18
|
-
setValue(selected)
|
|
24
|
+
setValue(selected);
|
|
19
25
|
}
|
|
20
|
-
}, [selected])
|
|
26
|
+
}, [selected]);
|
|
21
27
|
|
|
22
|
-
if (!children) return null
|
|
28
|
+
if (!children) return null;
|
|
23
29
|
|
|
24
30
|
const handleChange = (event, newValue) => {
|
|
25
31
|
if (onChange) {
|
|
26
|
-
onChange(tabList[newValue]?.id, event)
|
|
32
|
+
onChange(tabList[newValue]?.id, event);
|
|
27
33
|
}
|
|
28
34
|
if (setValue) {
|
|
29
|
-
setValue(tabList[newValue]?.id)
|
|
35
|
+
setValue(tabList[newValue]?.id);
|
|
30
36
|
}
|
|
31
|
-
}
|
|
37
|
+
};
|
|
32
38
|
|
|
33
39
|
const renderChildren = () => {
|
|
34
40
|
return React.Children.map(children, (child, index) => {
|
|
35
41
|
if (isTabPanel(child)) {
|
|
36
|
-
return React.cloneElement(child, { index, value: tabIndex })
|
|
42
|
+
return React.cloneElement(child, { index, value: tabIndex });
|
|
37
43
|
}
|
|
38
|
-
return child
|
|
39
|
-
})
|
|
40
|
-
}
|
|
44
|
+
return child;
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const panels = getPanelChildren(children);
|
|
49
|
+
|
|
50
|
+
const tabList = panels.map((panel) => {
|
|
51
|
+
const { id, tab, tabHref, disabled } = panel?.props || {};
|
|
52
|
+
return { tab, tabHref, disabled, id };
|
|
53
|
+
});
|
|
41
54
|
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
const tabList = panels.map(
|
|
45
|
-
(panel) => {
|
|
46
|
-
const { id, tab, tabHref, disabled } = panel?.props || {}
|
|
47
|
-
return { tab, tabHref, disabled, id }
|
|
48
|
-
},
|
|
49
|
-
)
|
|
55
|
+
const foundIndex = tabList.findIndex(({ id }) => id === value);
|
|
50
56
|
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
const tabIndex = foundIndex >= 0 ? foundIndex : 0
|
|
57
|
+
const tabIndex = foundIndex >= 0 ? foundIndex : 0;
|
|
54
58
|
|
|
55
59
|
return (
|
|
56
60
|
<>
|
|
57
61
|
<div className="qpp-c-tabs qpp-c-tabs--mui">
|
|
58
|
-
<MuiTabs
|
|
62
|
+
<MuiTabs
|
|
63
|
+
value={tabIndex}
|
|
64
|
+
onChange={handleChange}
|
|
65
|
+
aria-label={ariaLabel}
|
|
66
|
+
>
|
|
59
67
|
{tabList.map(({ tab, tabHref, disabled, id }, index) => (
|
|
60
68
|
<LinkTab
|
|
61
69
|
index={index}
|
|
@@ -70,19 +78,16 @@ const Tabs = ({ children, onChange, selected, defaultSelectedId, ariaLabel }) =>
|
|
|
70
78
|
</div>
|
|
71
79
|
{renderChildren()}
|
|
72
80
|
</>
|
|
73
|
-
)
|
|
74
|
-
}
|
|
81
|
+
);
|
|
82
|
+
};
|
|
75
83
|
|
|
76
84
|
Tabs.propTypes = {
|
|
77
85
|
ariaLabel: PropTypes.string,
|
|
78
86
|
selected: PropTypes.string,
|
|
79
87
|
defaultSelectedId: PropTypes.string,
|
|
80
88
|
onChange: PropTypes.func,
|
|
81
|
-
children: PropTypes.oneOfType([
|
|
82
|
-
|
|
83
|
-
PropTypes.array,
|
|
84
|
-
])
|
|
85
|
-
}
|
|
89
|
+
children: PropTypes.oneOfType([PropTypes.node, PropTypes.array]),
|
|
90
|
+
};
|
|
86
91
|
|
|
87
92
|
Tabs.defaultProps = {
|
|
88
93
|
ariaLabel: 'qpp tabs',
|
|
@@ -90,6 +95,6 @@ Tabs.defaultProps = {
|
|
|
90
95
|
defaultSelectedId: null,
|
|
91
96
|
onChange: null,
|
|
92
97
|
children: null,
|
|
93
|
-
}
|
|
98
|
+
};
|
|
94
99
|
|
|
95
|
-
export default Tabs
|
|
100
|
+
export default Tabs;
|