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

A-1205516692048401 | To add Allergies display control in new IPD dashboard #21

Merged
merged 11 commits into from
Nov 1, 2023
Merged
3 changes: 2 additions & 1 deletion public/i18n/locale_en.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@
"PENDING": "Pending",
"NOTE": "Note",
"NO_TREATMENTS_MESSAGE": "No IPD Medication is prescribed for this patient yet",
"ADD_TO_DRUG_CHART": "Add to Drug Chart"
"ADD_TO_DRUG_CHART": "Add to Drug Chart",
"NO_ALLERGENS_MESSAGE": "No Allergen is captured for this patient yet."
}
1 change: 0 additions & 1 deletion src/components/Dashboard/Dashboard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ export default function Dashboard(props) {
</main>
);
}

Dashboard.propTypes = {
hostData: PropTypes.object.isRequired,
};
57 changes: 56 additions & 1 deletion src/components/Dashboard/__snapshots__/Dashboard.spec.jsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,19 @@ exports[`Dashboard should match snapshot 1`] = `
</span>
</a>
</li>
<li
class="bx--side-nav__item"
>
<a
class="bx--side-nav__link cursor-pointer"
>
<span
class="bx--side-nav__link-text"
>
Allergies
</span>
</a>
</li>
<li
class="bx--side-nav__item"
>
Expand Down Expand Up @@ -203,7 +216,7 @@ exports[`Dashboard should match snapshot 1`] = `
class="bx--accordion__title"
dir="auto"
>
Treatments
Allergies
</div>
</button>
<div
Expand All @@ -214,6 +227,48 @@ exports[`Dashboard should match snapshot 1`] = `
</div>
</li>
</section>
<section
style="margin-bottom: 40px;"
>
<li
class="bx--accordion__item bx--accordion__item--active"
>
<button
aria-controls="accordion-item-4"
aria-expanded="true"
class="bx--accordion__heading"
type="button"
>
<svg
aria-hidden="true"
class="bx--accordion__arrow"
fill="currentColor"
focusable="false"
height="16"
preserveAspectRatio="xMidYMid meet"
viewBox="0 0 16 16"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11 8L6 13 5.3 12.3 9.6 8 5.3 3.7 6 3z"
/>
</svg>
<div
class="bx--accordion__title"
dir="auto"
>
Treatments
</div>
</button>
<div
class="bx--accordion__content"
id="accordion-item-4"
>
<div />
</div>
</li>
</section>
</ul>
</section>
</main>
Expand Down
3 changes: 3 additions & 0 deletions src/components/Dashboard/componentMapping.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@ export const componentMapping = {
TR: lazy(() =>
import("../../features/DisplayControls/Treatments/components/Treatments")
),
AL: lazy(() =>
import("../../features/DisplayControls/Allergies/components/Allergies")
),
};
2 changes: 2 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ const hostUrl = localStorage.getItem("host")
? "https://" + localStorage.getItem("host")
: "";
const RESTWS_V1 = hostUrl + "/openmrs/ws/rest/v1";
const FHIR2_R4 = hostUrl + "/openmrs/ws/fhir2/R4";
export const MEDICATIONS_BASE_URL = RESTWS_V1 + "/ipd/schedule/type/medication";
export const PRESCRIBED_AND_ACTIVE_DRUG_ORDERS_URL =
RESTWS_V1 + "/bahmnicore/drugOrders/prescribedAndActive";
export const ALLERGIES_BASE_URL = FHIR2_R4 + "/AllergyIntolerance";

export const medicationFrequency = {
START_TIME_DURATION_FREQUENCY: "START_TIME_DURATION_FREQUENCY",
Expand Down
169 changes: 169 additions & 0 deletions src/features/DisplayControls/Allergies/components/Allergies.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import {
DataTable,
DataTableSkeleton,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "carbon-components-react";
import React, { useEffect, useState } from "react";
import { useFetchAllergiesIntolerance } from "../hooks/useFetchAllergiesIntolerance";
import PropTypes from "prop-types";
import "../styles/Allergies.scss";
import { FormattedMessage } from "react-intl";

const Allergies = (props) => {
const { patientId } = props;

const { allergiesData, isLoading } = useFetchAllergiesIntolerance(patientId);
const [rows, setRows] = useState([]);
const NoAllergenMessage = (
<FormattedMessage
id={"NO_ALLERGENS_MESSAGE"}
defaultMessage={"No Allergen is captured for this patient yet."}
/>
);

useEffect(() => {
if (allergiesData && allergiesData.entry) {
const allergies = [];
allergiesData.entry?.map((allergy) => {
allergies.push({
abinaya-u marked this conversation as resolved.
Show resolved Hide resolved
allergen: allergy.resource.code.coding[0].display,
id: allergy.resource.id,
severity: getSeverity(allergy.resource.criticality),
reaction: getAllergyReactions(allergy.resource.reaction),
comments: getComments(allergy.resource.note),
sortWeight: getSortingWait(getSeverity(allergy.resource.criticality)),
});
abinaya-u marked this conversation as resolved.
Show resolved Hide resolved
});
setRows(sortedRow(allergies));
}
}, [allergiesData]);

const getSortingWait = (severity) => {
if (severity === "Severe") return -1;
if (severity === "Moderate") return 0;
return 1;
};

const getSeverity = (criticality) => {
if (criticality == "unable-to-assess") return "Moderate";
else if (criticality == "high") return "Severe";
else return "Mild";
};

const getComments = (notes) =>
notes && notes.length > 0 ? notes[0].text : "";

const getAllergyReactions = (reactions) => {
let allergyReactions = "";
if (reactions && reactions.length > 0) {
reactions[0].manifestation.map((reaction) => {
allergyReactions =
allergyReactions != ""
? `${allergyReactions}, ${reaction.coding[0].display}`
: `${reaction.coding[0].display}`;
});
}
return allergyReactions;
};

const headers = [
abinaya-u marked this conversation as resolved.
Show resolved Hide resolved
{
key: "allergen",
header: "Allergen",
},
{
key: "severity",
header: "Severity",
},
{
key: "reaction",
header: "Reaction",
},
{
key: "comments",
header: "Comments",
},
];

const sortedRow = (rows) => {
const sortedRows = rows
.sort((a, b) => {
if (a === b) return 0;
return a.allergen > b.allergen ? 1 : -1;
})
.sort((a, b) => a.sortWeight - b.sortWeight);
return sortedRows;
};

if (isLoading)
return (
<DataTableSkeleton
data-testid="datatable-skeleton"
headers={headers}
aria-label="sample table"
zebra="true"
/>
);

return allergiesData.entry === undefined ? (
<div className="no-allergen-message"> {NoAllergenMessage} </div>
) : (
<DataTable
rows={rows}
headers={headers}
useZebraStyles={true}
data-testid="datatable"
>
{({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
<Table {...getTableProps()} useZebraStyles>
<TableHead>
<TableRow>
{headers.map((header, index) => (
<TableHeader
key={index + header.key}
{...getHeaderProps({ header })}
isSortable={header.key === "severity"}
>
{header.header}
</TableHeader>
))}
</TableRow>
</TableHead>
<TableBody>
{rows.map((row, index) => (
<TableRow
key={index + row.id}
{...getRowProps({ row })}
data-testid="table-body-row"
>
{row.cells.map((cell) => (
<TableCell
key={cell.id}
className={
cell.id.includes("severity") && cell.value == "Severe"
? "high-severity-color"
: ""
}
>
{cell.value}
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
)}
</DataTable>
);
};

Allergies.propTypes = {
patientId: PropTypes.string.isRequired,
};

export default Allergies;
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { render, waitFor, screen } from "@testing-library/react";
import axios from "axios";
import React from "react";
import { mockAllergiesIntolerenceResponse } from "./AllergiesTestUtils";
import Allergies from "./Allergies";
import "@testing-library/jest-dom/extend-expect";

jest.mock("axios");
describe("Allergies", () => {
it("should render Allergies", () => {
axios.get.mockResolvedValue(mockAllergiesIntolerenceResponse);
render(<Allergies patientId={"__test_patient_uuid__"} />);
expect(screen.getByText("Allergen")).toBeInTheDocument();
});

it("should show data in the table", async () => {
axios.get.mockResolvedValue(mockAllergiesIntolerenceResponse);
render(<Allergies patientId={"__test_patient_uuid__"} />);

await waitFor(() => {
expect(screen.getByTestId(/datatable/i)).toBeInTheDocument();
});
expect(screen.getByText("Beef")).toBeInTheDocument();
expect(screen.getByText(/test comment/i)).toBeInTheDocument();
});

it("should highlight severity column when the value is high", async () => {
axios.get.mockResolvedValue(mockAllergiesIntolerenceResponse);
render(<Allergies patientId={"__test_patient_uuid__"} />);

await waitFor(() => {
expect(screen.getByTestId(/datatable/i)).toBeInTheDocument();
});
expect(screen.getAllByRole("cell", { name: /severe/i })[0]).toHaveClass(
"high-severity-color"
);
expect(screen.getAllByRole("cell", { name: /mild/i })[0]).not.toHaveClass(
"high-severity-color"
);
});

it("should sort Allergen in ASC order based on severity", async () => {
axios.get.mockResolvedValue(mockAllergiesIntolerenceResponse);
render(<Allergies patientId={"__test_patient_uuid__"} />);

await waitFor(() => {
expect(screen.getByTestId(/datatable/i)).toBeInTheDocument();
});

expect(screen.getAllByTestId("table-body-row")[0]).toHaveTextContent(
"Beef"
);
expect(screen.getAllByTestId("table-body-row")[0]).toHaveTextContent(
"Severe"
);
expect(screen.getAllByTestId("table-body-row")[1]).toHaveTextContent(
"Milk product"
);
expect(screen.getAllByTestId("table-body-row")[1]).toHaveTextContent(
"Severe"
);
});

it("should show table skeleton on loading", async () => {
axios.get.mockResolvedValue(mockAllergiesIntolerenceResponse);
render(<Allergies patientId={"__test_patient_uuid__"} />);

expect(screen.getByTestId("datatable-skeleton")).toBeInTheDocument();

await waitFor(() => {
expect(screen.getByTestId(/datatable/i)).toBeInTheDocument();
});

expect(screen.queryByTestId("datatable-skeleton")).not.toBeInTheDocument();
});

it("should show no data message when there is no data", async () => {
axios.get.mockResolvedValue({ data: { entry: undefined } });
render(<Allergies patientId={"__test_patient_uuid__"} />);

await waitFor(() => {
expect(
screen.getByText(/No Allergen is captured for this patient yet/i)
).toBeInTheDocument();
});
});
});
Loading
Loading