Skip to content

Commit

Permalink
A-1205516692048401 | To add Allergies display control in new IPD dash…
Browse files Browse the repository at this point in the history
…board (#21)

* Abinaya|Tanya add. allergies dislay cntrl

Co-authored-by: Tanya-tw <[email protected]>

* Abinaya|Tanya add. styles adn test for allergies display cntrl

Co-authored-by: Tanya-tw <[email protected]>

* Abinaya|Tanya add. datatable-skeleton on loading

* Abinaya|Tanya refactor. updated allergy display controls with early returns

* [Tanya|Abinaya] add allergies under display control and refactor the names and severity

* [Tanya|Abinaya] remove allergies display control from entries and update no allergen message

* Update webpack.config.js

* [Tanya|Abinaya] add updated snapshot

* [Tanya|Abinaya] add. test case for no allergen message

* [Tanya|Abinaya] update file structure for allergies component

* [Tanya|Abinaya] add updated snapshot

---------

Co-authored-by: Abinaya U <[email protected]>
  • Loading branch information
tanyaa-tw and abinaya-u authored Nov 1, 2023
1 parent d31aab2 commit a4fb1fd
Show file tree
Hide file tree
Showing 14 changed files with 589 additions and 5 deletions.
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({
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)),
});
});
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 = [
{
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

0 comments on commit a4fb1fd

Please sign in to comment.