Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OH2-193 | Display dashboard components base on user role/permission #535

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26,787 changes: 17,859 additions & 8,928 deletions api/oh.yaml

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion src/components/accessories/dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { DashboardContent } from "./dashboardContent/DashboardContent";
import "./styles.scss";
import { IStateProps, TProps } from "./types";
import { Chart, registerables } from "chart.js";
import { Permission } from "../../../libraries/permissionUtils/Permission";

Chart.register(...registerables);

Expand All @@ -24,7 +25,9 @@ const Dashboard: FunctionComponent<TProps> = ({ userCredentials }) => {
breadcrumbMap={breadcrumbMap}
/>
<div className="dashboard__background">
<DashboardContent />
<Permission require="dashboard.access">
<DashboardContent />
</Permission>
</div>
<Footer />
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,57 @@
import React, { FunctionComponent } from "react";
import { DashboardFilter } from "./filter/DashboardFilter";
import { GridLayoutToolbox } from "../layouts/toolbox/GridLayoutToolBox";
import GridLayoutToolbox from "../layouts/toolbox/GridLayoutToolBox";
import GridLayoutContainer from "../layouts/container/GridLayoutContainer";
import { setDashboardPeriod } from "../../../../state/dashboard/actions";
import { useDispatch } from "react-redux";
import { useDispatch, useSelector } from "react-redux";
import "./styles.scss";
import { IState } from "../../../../types";
import { TAPIResponseStatus } from "../../../../state/types";
import { CircularProgress } from "@material-ui/core";
import { Navigate } from "react-router";
import { PATHS } from "../../../../consts";

export const DashboardContent: FunctionComponent = () => {
const dispatch = useDispatch();
const handlePeriodChange = (value: string[]) => {
dispatch(setDashboardPeriod(value));
};

const authUserStatus = useSelector<IState, TAPIResponseStatus>(
(state) => state.main.authentication.status ?? "IDLE"
);

return (
<div className="dashboard__content">
<div className="dashboard__main">
<div className="dashboard__main-content">
<div className="dashboard__main-header">
<DashboardFilter onPeriodChange={handlePeriodChange} />
</div>
<div className="dashboard__main-body">
<GridLayoutContainer />
<>
{authUserStatus === "SUCCESS" && (
<div className="dashboard__content">
<div className="dashboard__main">
<div className="dashboard__main-content">
<div className="dashboard__main-header">
<DashboardFilter onPeriodChange={handlePeriodChange} />
</div>
<div className="dashboard__main-body">
<GridLayoutContainer />
</div>
</div>
<div className="dashboard__main-side">
<GridLayoutToolbox />
</div>
</div>
</div>
<div className="dashboard__main-side">
<GridLayoutToolbox />
</div>
</div>
</div>
)}

{authUserStatus === "LOADING" && (
<CircularProgress
style={{
marginLeft: "50%",
marginTop: "200px",
position: "relative",
}}
/>
)}

{authUserStatus === "FAIL" && <Navigate to={PATHS.login} />}
</>
);
};
102 changes: 89 additions & 13 deletions src/components/accessories/dashboard/layouts/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@ import {
LayoutConfiguration,
TDashboardComponent,
} from "./types";
import { TPermission } from "../../../../types";
import { getAuthenticationFromSession } from "../../../../libraries/authUtils/getAuthenticationFromSession";

/**
* This array contains all dashboard widgets
* If a dashboard widget is added, its name should be added in this array
* and a `switch case` should be added in the switch control of a component
* src\components\accessories\dashboard\layouts\item\GridLayoutItem.tsx
*/
export const DASHBOARDS = [
export const DASHBOARDS: TDashboardComponent[] = [
"opdByAgeType",
"opdBySex",
"admissionBySex",
Expand All @@ -24,6 +26,23 @@ export const DASHBOARDS = [
"dischargeByType",
];

/**
* This array is a map of dashboard widgets and related permissions
*/
export const DASHBOARDS_PERMISSIONS: Record<TDashboardComponent, TPermission> =
{
admissionByAgeType: "admission.read",
admissionBySex: "admission.read",
admissionByType: "admission.read",
admissionByWard: "admission.read",
dischargeByAgeType: "discharges.read",
dischargeBySex: "discharges.read",
dischargeByType: "discharges.read",
dischargeByWard: "discharges.read",
opdByAgeType: "opd.read",
opdBySex: "opd.read",
};

export const defaultLayoutConfig: Layouts = {
lg: generateLayout("lg"),
md: generateLayout("md"),
Expand Down Expand Up @@ -77,6 +96,8 @@ export const getBreakpointFromWidth = (width: number): string => {
* Get dashboard label's translation key
* @param dashboardKey Dashboard key
* @returns Return the translation key
* @todo Create a map like DASHBOARDS_PERMISSIONS use it
* to get dashboards widget labels.
*/
export function getDashboardLabel(dashboardKey: TDashboardComponent): string {
switch (dashboardKey) {
Expand Down Expand Up @@ -144,6 +165,10 @@ export function removeDuplicates(input: Layouts): Layouts {
export function removeDoubles(input1: Layouts, input2: Layouts): Layouts {
let cleanInput: Layouts = {};

if (Object.keys(input2).length === 0) {
return input1;
}

Object.keys(input1).forEach((breakpoint) => {
let breakpointConfig = input1[breakpoint].filter((layout) => {
return !input2[breakpoint].some((layout1) => layout1.i == layout.i);
Expand All @@ -155,13 +180,47 @@ export function removeDoubles(input1: Layouts, input2: Layouts): Layouts {
return cleanInput;
}

/**
* This function uses permissions stored in session storage
* to check if Authenticated user has permission to vie
* @returns Allowed dashboard widgets
*/
export function allowedDashboards(): TDashboardComponent[] {
let allowedDashboards: TDashboardComponent[] = [];

let permissions: TPermission[] = [];
try {
permissions = getAuthenticationFromSession().permissions;
} catch (error) {
//console.log(error);
}

DASHBOARDS.forEach((dash) => {
if (permissions.includes(DASHBOARDS_PERMISSIONS[dash])) {
allowedDashboards.push(dash);
}
});

return allowedDashboards;
}

export function isEmptyLayout(input: Layouts): boolean {
let nbWidgets = 0;

["lg", "md", "sm", "xs", "xxs"].forEach((breakpoint) => {
nbWidgets += input[breakpoint] ? input[breakpoint].length : 0;
});

return nbWidgets == 0;
}

/**
* Generate layout for random Dashboards
* @param nbDashboard Number of dashboard to generate
* @returns Returns the layout config for specified Dashboards number
*/
export function randomLayout(nbDashboard: number): Layouts {
let randomDashboards = randomItems(DASHBOARDS, nbDashboard);
let randomDashboards = randomItems(allowedDashboards(), nbDashboard);

return removeDuplicates({
lg: generateLayout("lg", randomDashboards),
Expand Down Expand Up @@ -196,7 +255,7 @@ export function toolboxDashboards(
});

["lg", "md", "sm", "xs", "xxs"].forEach((breakpoint) => {
unknownDashboard[breakpoint] = DASHBOARDS.filter(
unknownDashboard[breakpoint] = allowedDashboards().filter(
(dashboard) => !knownDashboard[breakpoint].includes(dashboard)
);
});
Expand Down Expand Up @@ -274,7 +333,7 @@ export function generateLayout(
dashboards?: string[]
): Layout[] {
if (!dashboards || dashboards.length == 0) {
dashboards = DASHBOARDS;
dashboards = allowedDashboards();
}

return dashboards.map((dashboardKey, index) => {
Expand Down Expand Up @@ -398,15 +457,32 @@ export const addWidget = (
breakpoint: LayoutBreakpoints
): Layouts => {
let layouts: Layouts = {};
Object.keys(input).forEach((currentBreakpoint) => {
let breakpointConfig = [
...input[currentBreakpoint],
breakpoint === currentBreakpoint
? widget
: generateLayout(currentBreakpoint as LayoutBreakpoints, [widget.i])[0],
];
layouts[currentBreakpoint] = breakpointConfig;
});

if (Object.keys(input).length === 0) {
["lg", "md", "sm", "xs", "xxs"].forEach((currentBreakpoint) => {
let breakpointConfig = [
...(input[currentBreakpoint] ?? []),
breakpoint === currentBreakpoint
? widget
: generateLayout(currentBreakpoint as LayoutBreakpoints, [
widget.i,
])[0],
];
layouts[currentBreakpoint] = breakpointConfig;
});
} else {
Object.keys(input).forEach((currentBreakpoint) => {
let breakpointConfig = [
...input[currentBreakpoint],
breakpoint === currentBreakpoint
? widget
: generateLayout(currentBreakpoint as LayoutBreakpoints, [
widget.i,
])[0],
];
layouts[currentBreakpoint] = breakpointConfig;
});
}

return layouts;
};
Expand Down
Loading
Loading