diff --git a/assets/logo/logo.png b/assets/logo/logo.png new file mode 100644 index 0000000..52299ae Binary files /dev/null and b/assets/logo/logo.png differ diff --git a/assets/logo/moh_logo_without_word.png b/assets/logo/moh_logo_without_word.png new file mode 100644 index 0000000..8cd3f42 Binary files /dev/null and b/assets/logo/moh_logo_without_word.png differ diff --git a/src/completed-list/completed-list.component.tsx b/src/completed-list/completed-list.component.tsx new file mode 100644 index 0000000..d62863b --- /dev/null +++ b/src/completed-list/completed-list.component.tsx @@ -0,0 +1,219 @@ +import React, { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useGetOrdersWorklist } from "../work-list/work-list.resource"; +import { ErrorState, usePagination } from "@openmrs/esm-framework"; +import { + DataTable, + DataTableSkeleton, + Pagination, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableHeader, + TableRow, + TableToolbar, + TableToolbarContent, + TableToolbarSearch, + Layer, + Tile, + DatePicker, + DatePickerInput, + Select, + SelectItem, + Tag, + Dropdown, +} from "@carbon/react"; +import styles from "./completed-list.scss"; +import { getStatusColor } from "../utils/functions"; + +interface CompletedlistProps { + fulfillerStatus: string; +} + +const CompletedList: React.FC = ({ fulfillerStatus }) => { + const { t } = useTranslation(); + + const [activatedOnOrAfterDate, setActivatedOnOrAfterDate] = useState(""); + + const { workListEntries, isLoading } = useGetOrdersWorklist( + activatedOnOrAfterDate, + fulfillerStatus + ); + + const pageSizes = [10, 20, 30, 40, 50]; + const [page, setPage] = useState(1); + const [currentPageSize, setPageSize] = useState(10); + const [nextOffSet, setNextOffSet] = useState(0); + + const { + goTo, + results: paginatedWorkListEntries, + currentPage, + } = usePagination(workListEntries, currentPageSize); + + // get picked orders + let columns = [ + { id: 0, header: t("orderNumber", "Order Number"), key: "orderNumber" }, + { + id: 1, + header: t("accessionNumber", "Accession Number"), + key: "accessionNumber", + }, + { id: 2, header: t("test", "Test"), key: "test" }, + { id: 3, header: t("action", "Action"), key: "action" }, + { id: 4, header: t("status", "Status"), key: "status" }, + { id: 5, header: t("orderer", "Orderer"), key: "orderer" }, + { id: 6, header: t("orderType", "Order Type"), key: "orderType" }, + { id: 7, header: t("urgency", "Urgency"), key: "urgency" }, + ]; + + const tableRows = useMemo(() => { + return paginatedWorkListEntries?.map((entry, index) => ({ + ...entry, + id: entry.uuid, + orderNumber: { content: {entry.orderNumber} }, + accessionNumber: { content: {entry.accessionNumber} }, + test: { content: {entry.concept.display} }, + action: { content: {entry.action} }, + status: { + content: ( + <> + + + {entry.fulfillerStatus} + + + + ), + }, + orderer: { content: {entry.orderer.display} }, + orderType: { content: {entry.orderType.display} }, + urgency: { content: {entry.urgency} }, + })); + }, [paginatedWorkListEntries]); + + if (isLoading) { + return ; + } + + if (paginatedWorkListEntries?.length >= 0) { + return ( +
+
+ + {({ + rows, + headers, + getHeaderProps, + getTableProps, + getRowProps, + onInputChange, + }) => ( + + + + + + { + setActivatedOnOrAfterDate(event.target.value); + }} + type="date" + value={activatedOnOrAfterDate} + /> + + + + + + + +
+ + + + {headers.map((header) => ( + + {header.header} + + ))} + + + + {rows.map((row, index) => { + return ( + + + {row.cells.map((cell) => ( + + {cell.value?.content ?? cell.value} + + ))} + + + ); + })} + +
+ {rows.length === 0 ? ( +
+ +
+

+ {t( + "noCompletedListToDisplay", + "No Completed List to display" + )} +

+
+
+
+ ) : null} + { + if (pageSize !== currentPageSize) { + setPageSize(pageSize); + } + if (page !== currentPage) { + goTo(page); + } + }} + /> +
+ )} +
+
+ ); + } +}; + +export default CompletedList; diff --git a/src/completed-list/completed-list.resource.ts b/src/completed-list/completed-list.resource.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/completed-list/completed-list.scss b/src/completed-list/completed-list.scss new file mode 100644 index 0000000..0be385f --- /dev/null +++ b/src/completed-list/completed-list.scss @@ -0,0 +1,232 @@ +@use '@carbon/styles/scss/spacing'; +@use '@carbon/styles/scss/type'; +@import "~@openmrs/esm-styleguide/src/vars"; +@import '../root.scss'; + +title { + width: 6.938rem; + height: 1.75rem; + margin: 0.438rem 22.875rem 0.813rem 0; + font-family: IBMPlexSans; + font-size: 1.25rem; + font-weight: normal; + font-stretch: normal; + font-style: normal; + line-height: 1.4; + letter-spacing: normal; +} + +.link { + text-decoration: none; +} + +.breadcrumbsSlot { + grid-row: 1 / 2; + grid-column: 1 / 2; +} + +.orderTabsContainer { + height: 100%; + width: 100%; + + :global(.cds--tab-content) { + padding: 0 !important; + } +} + +.orderTabs { + grid-column: 'span 2'; + padding: 0 spacing.$spacing-05; +} + +.newListButton { + width: fit-content; + justify-self: end; + align-self: center; +} + +.tabsContainer { + background-color: $ui-02; + + :global(.cds--tabs__nav-item--selected) { + box-shadow: inset 0 2px 0 0 var(--brand-03) !important; + } + + :global(.cds--tab--list) button { + max-width: 12rem !important; + } +} + +.hiddenTabsContent, +.tabs .hiddenTabsContent { + display: none; +} + +.patientListTableContainer { + grid-row: 3 / 4; + grid-column: 1 / 2; + height: 100%; + margin: 0.5rem spacing.$spacing-05; + background-color: $ui-01; + border: 0.5px solid #e0e0e0; + + :global(.cds--data-table-container) { + padding-top: 0 !important; + } + + tbody>tr>:nth-child(2) { + white-space: nowrap; + } + + :global(.cds--data-table td) { + height: unset !important; + } + + :global(.cds--data-table--zebra) tbody tr[data-parent-row]:nth-child(4n + 1) td { + background-color: $ui-02; + border-bottom: 1px solid $ui-03; + border-top: 1px solid $ui-03; + } + + :global(.cds--data-table--zebra) tbody tr[data-parent-row]:nth-child(4n + 3) td { + background-color: $ui-01; + border-bottom: 1px solid $ui-03; + } +} + +.tableContainer { + background-color: $ui-01; + margin: 0 spacing.$spacing-05; + padding: 0; + + a { + text-decoration: none; + } + + th { + color: $text-02; + } + + :global(.cds--data-table) { + background-color: $ui-03; + } + + .toolbarContent { + height: spacing.$spacing-07; + margin-bottom: spacing.$spacing-02; + } +} + +.activePatientsTable tr:last-of-type { + td { + border-bottom: none; + } +} + +.headerBtnContainer { + background-color: $ui-background; + padding: spacing.$spacing-05; + text-align: right; +} + +.searchContainer { + display: flex; + align-items: center; + flex-direction: row-reverse; + padding-top: 0.5rem; + + :global(.cds--search-magnifier-icon) { + z-index: 0 !important; + } + + input { + background-color: #fff; + } +} + +.addOrderBtn { + width: 10rem !important; + padding: 0.5rem !important; + margin-left: 1rem; + margin-right: 1rem; +} + +.patientSearch { + width: 25rem; + border-bottom-color: $ui-03; +} + +.locationFilter { + width: 25rem; +} + +.search { + width: 100%; + max-width: 16rem; + background-color: $ui-02; + border-bottom-color: $ui-03; +} + +.container { + background-color: $ui-01; +} + +.expandedLabQueueVisitRow { + :global(.cds--tab-content) { + padding: 0.5rem 0; + } + + td { + padding: 0.5rem; + + >div { + max-height: max-content !important; + background-color: $ui-02; + } + } + + th[colspan] td[colspan]>div:first-child { + padding: 0 1rem; + } +} + +.tableLayerSearch { + margin: 5px; + padding: 10px; + +} + +.dropDownContainer { + padding: 10px; +} + +.toolbar { + display: flex; + align-items: center; + padding: 5px; + margin: 10px; +} + + +.tileContainer { + background-color: $ui-02; + border-top: 1px solid $ui-03; + padding: 5rem 0; +} + +.tile { + margin: auto; + width: fit-content; +} + +.tileContent { + display: flex; + flex-direction: column; + align-items: center; +} + +.content { + @include type.type-style('heading-compact-02'); + color: $text-02; + margin-bottom: 0.5rem; +} \ No newline at end of file diff --git a/src/components/overlay/hook.ts b/src/components/overlay/hook.ts new file mode 100644 index 0000000..4a9d4ec --- /dev/null +++ b/src/components/overlay/hook.ts @@ -0,0 +1,47 @@ +import { getGlobalStore } from "@openmrs/esm-framework"; +import { useEffect, useState } from "react"; + +interface OverlayStore { + isOverlayOpen: boolean; + component?: any; + header: string; +} + +const initialState = { isOverlayOpen: false, component: Function, header: "" }; + +const getOverlayStore = () => { + return getGlobalStore("laboratory", initialState); +}; + +export const launchOverlay = (headerTitle: string, componentToRender) => { + const store = getOverlayStore(); + store.setState({ + isOverlayOpen: true, + component: componentToRender, + header: headerTitle, + }); +}; + +export const closeOverlay = () => { + const store = getOverlayStore(); + store.setState({ component: null, isOverlayOpen: false }); +}; + +export const useOverlay = () => { + const [overlay, setOverlay] = useState(); + + useEffect(() => { + function update(state: OverlayStore) { + setOverlay(state); + } + + update(getOverlayStore().getState()); + getOverlayStore().subscribe(update); + }, []); + + return { + isOverlayOpen: overlay?.isOverlayOpen, + component: overlay?.component, + header: overlay?.header, + }; +}; diff --git a/src/components/overlay/overlay.component.tsx b/src/components/overlay/overlay.component.tsx new file mode 100644 index 0000000..25351b2 --- /dev/null +++ b/src/components/overlay/overlay.component.tsx @@ -0,0 +1,52 @@ +import React from "react"; +import { Button, Header } from "@carbon/react"; +import { ArrowLeft, Close } from "@carbon/react/icons"; +import { useLayoutType } from "@openmrs/esm-framework"; +import styles from "./overlay.scss"; +import { useTranslation } from "react-i18next"; +import { closeOverlay, useOverlay } from "./hook"; + +const Overlay: React.FC = () => { + const { header, component, isOverlayOpen } = useOverlay(); + const layout = useLayoutType(); + const { t } = useTranslation(); + return ( + <> + {isOverlayOpen && ( +
+ {layout !== "tablet" ? ( +
+
{header}
+ +
+ ) : ( +
closeOverlay()} + aria-label={t("tabletOverlay", "Tablet overlay")} + className={styles.tabletOverlayHeader} + > + +
{header}
+
+ )} +
{component}
+
+ )} + + ); +}; + +export default Overlay; diff --git a/src/components/overlay/overlay.scss b/src/components/overlay/overlay.scss new file mode 100644 index 0000000..d6e1e3a --- /dev/null +++ b/src/components/overlay/overlay.scss @@ -0,0 +1,93 @@ +@use '@carbon/styles/scss/spacing'; +@use '@carbon/styles/scss/type'; +@import '~@openmrs/esm-styleguide/src/vars'; +@import '../../root.scss'; + +.desktopOverlay { + position: fixed; + top: spacing.$spacing-09; + width: 37rem; + right: 0; + bottom: 0; + border-left: 1px solid $text-03; + background-color: $ui-01; + overflow-y: auto; + height: calc(100vh - 3rem); +} + +.desktopOverlay::after { + height: 100%; + border-left: 1px solid $text-03; +} + +.tabletOverlay { + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 9999; + background-color: $ui-01; + overflow-y: scroll; + -ms-overflow-style: none; + scrollbar-width: none; + + &::-webkit-scrollbar { + width: 0; + } + + &>div { + margin-top: spacing.$spacing-09; + } +} + +.tabletOverlayHeader { + button { + @include brand-01(background-color); + } + + .headerContent { + color: $ui-02; + } +} + +.desktopHeader { + display: flex; + justify-content: space-between; + align-items: center; + background-color: $ui-03; + border-bottom: 1px solid $text-03; + position: sticky; + position: -webkit-sticky; + width: 100%; + z-index: 1000; + top: 0; +} + +.headerContent { + @include type.type-style('heading-compact-02'); + padding: 0 spacing.$spacing-05; + color: $ui-05; +} + +.closeButton { + background-color: $ui-background; + color: $ui-05; + fill: $ui-05; +} + +/* Desktop */ +:global(.omrs-breakpoint-gt-tablet) { + .overlayContent { + padding: 0 0 0 0; + overflow-y: auto; + } +} + +/* Tablet */ +:global(.omrs-breakpoint-lt-desktop) { + .overlayContent { + padding: 0 0 0 0; + overflow-y: auto; + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 1de3af6..e42a09e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -77,6 +77,11 @@ export const editResultsDialog = getAsyncLifecycle( options ); +export const reviewItemDialog = getAsyncLifecycle( + () => import("./review-list/dialog/review-item.component"), + options +); + export function startupApp() { defineConfigSchema(moduleName, configSchema); } diff --git a/src/laboratory.component.tsx b/src/laboratory.component.tsx index 96ef690..7b5969c 100644 --- a/src/laboratory.component.tsx +++ b/src/laboratory.component.tsx @@ -2,6 +2,7 @@ import React from "react"; import { LaboratoryHeader } from "./header/laboratory-header.component"; import LaboratorySummaryTiles from "./summary-tiles/laboratory-summary-tiles.component"; import LaboratoryQueueList from "./queue-list/laboratory-tabs.component"; +import Overlay from "./components/overlay/overlay.component"; const Laboratory: React.FC = () => { return ( @@ -9,6 +10,7 @@ const Laboratory: React.FC = () => { + ); }; diff --git a/src/patient-chart/laboratory-order.component.tsx b/src/patient-chart/laboratory-order.component.tsx index 77c6642..f4698e1 100644 --- a/src/patient-chart/laboratory-order.component.tsx +++ b/src/patient-chart/laboratory-order.component.tsx @@ -51,6 +51,7 @@ import { useReactToPrint } from "react-to-print"; import SendEmailDialog from "./results-summary/send-email-dialog.component"; import PrintResultsSummary from "./results-summary/print-results-summary.component"; import { EncounterResponse } from "./laboratory-item/view-laboratory-item.resource"; +import { useGetPatientByUuid } from "../utils/functions"; interface LaboratoryOrderOverviewProps { patientUuid: string; @@ -95,10 +96,10 @@ const LaboratoryOrder: React.FC = ({ let columns = [ { id: 0, - header: t("orderDate", "Order Date"), + header: t("orderDate", "Test Date"), key: "orderDate", }, - { id: 1, header: t("orders", "Order"), key: "orders" }, + { id: 1, header: t("tests", "Tests"), key: "orders" }, { id: 2, header: t("location", "Location"), key: "location" }, { id: 3, header: t("status", "Status"), key: "status" }, { id: 4, header: t("actions", "Action"), key: "actions" }, @@ -152,6 +153,8 @@ const LaboratoryOrder: React.FC = ({ }; const PrintButtonAction: React.FC = ({ encounter }) => { + const { patient } = useGetPatientByUuid(encounter.patient.uuid); + const [isPrinting, setIsPrinting] = useState(false); const contentToPrintRef = useRef(null); @@ -180,7 +183,10 @@ const LaboratoryOrder: React.FC = ({ return (
- +
= ({ encounterResponse, + patient, }) => { + const { t } = useTranslation(); + + const filteredItems = encounterResponse.obs.filter( + (ob) => ob?.order?.type === "testorder" + ); + + const results = useMemo(() => { + let groupedResults = []; + + filteredItems.forEach((element) => { + groupedResults[element.order.display] = element; + }); + return groupedResults; + }, [filteredItems]); return (
-
+
+
+
+
+ {"logo"} + + {encounterResponse.visit.location.display} + +
+
+ + Code : + + + District : + +
+
+
+ +
+ + + {patient?.person?.display} + + , + {patient?.person?.gender === "M" + ? " Male" + : patient?.person?.gender === "F" + ? " Female" + : " Unknown"} + , + + {" "} + {formatDate(parseDate(patient?.person?.birthdate), { + mode: "standard", + time: false, + })}{" "} + + + + HIV Clinic No. : + {patient?.identifiers.length + ? patient?.identifiers.find((identifier: Identifier) => { + return ( + identifier.identifierType.uuid === + "e1731641-30ab-102d-86b0-7a5022ba4115" + ); + })?.identifier + : "--"} + + + Patient Unique Code (UIC). : + {patient?.identifiers.length > 0 + ? patient?.identifiers.find((identifier: Identifier) => { + return ( + identifier.identifierType.uuid === + "877169c4-92c6-4cc9-bf45-1ab95faea242" + ); + })?.identifier + : "--"} + +
+
- - Date : + + Prepared By : {encounterResponse?.auditInfo?.creator?.display} + + + Date Requested : {formatDate(parseDate(encounterResponse.encounterDatetime), { time: false, })} - - Ordered By : {encounterResponse?.auditInfo?.creator?.display} -
-
+
+
+
+ {/*
+
- Results Ordered + + Test Results +
-
+
*/} +
+ {Object.keys(results).length > 0 && ( + + )}
+
- +
+ + Results Reviewed / Authorized by : + + {encounterResponse?.auditInfo?.creator?.display} + + +
+
+ + Sign : .............................................{" "} + + Date : ............................ + + +
); diff --git a/src/patient-chart/results-summary/print-results-summary.scss b/src/patient-chart/results-summary/print-results-summary.scss index 05c27f0..1b33941 100644 --- a/src/patient-chart/results-summary/print-results-summary.scss +++ b/src/patient-chart/results-summary/print-results-summary.scss @@ -13,11 +13,11 @@ @media print { @page { size: auto; - margin: 10px; + margin: 5px; } .section { - margin: 10px; + margin: 5px; } .headerBtnContainer { @@ -84,4 +84,22 @@ } } + table { + font-family: Arial, sans-serif; + border-collapse: collapse; + width: 100%; + } + + td, th { + border: 1px solid #000; + text-align: left; + font-size: 8px; + padding: 8px; + width: 80px; + } + + th { + background-color: #f2f2f2; + } + } \ No newline at end of file diff --git a/src/patient-chart/results-summary/print-results-table.component.tsx b/src/patient-chart/results-summary/print-results-table.component.tsx new file mode 100644 index 0000000..abcd393 --- /dev/null +++ b/src/patient-chart/results-summary/print-results-table.component.tsx @@ -0,0 +1,83 @@ +import React from "react"; +import styles from "./print-results-summary.scss"; +import { GroupMember } from "../laboratory-order.resource"; + +interface PrintResultsTableProps { + groupedResults: any[]; +} + +interface ResultsRowProps { + groupMembers: GroupMember[]; +} + +const PrintResultsTable: React.FC = ({ + groupedResults, +}) => { + const RowTest: React.FC = ({ groupMembers }) => { + return ( + <> + {groupMembers?.map((element, index) => { + return ( + + {typeof element.value === "number" ? ( + <> + {element?.concept.display} + + {element?.value} + + -- + + -- + + ) : typeof element.value === "object" ? ( + <> + {element?.concept.display} + + {element?.value.display} + + -- + + -- + + ) : ( + {element?.display} + )} + + ); + })} + + ); + }; + return ( +
+ + + + + + + + + +
TestsResultReference RangeUnits
+ + + {Object.keys(groupedResults).map((test) => ( + + + {test} + +
+ +
+ + ))} + + +
+ ); +}; + +export default PrintResultsTable; diff --git a/src/patient-chart/results-summary/results-summary.resource.tsx b/src/patient-chart/results-summary/results-summary.resource.tsx index 02e2546..9c68517 100644 --- a/src/patient-chart/results-summary/results-summary.resource.tsx +++ b/src/patient-chart/results-summary/results-summary.resource.tsx @@ -172,3 +172,14 @@ export function useGetConceptById(conceptUuid: string) { isError: error, }; } + +export async function GetPatientByUuid(uuid: string) { + const abortController = new AbortController(); + + return openmrsFetch(`/ws/rest/v1/patient/${uuid}`, { + headers: { + "Content-Type": "application/json", + }, + signal: abortController.signal, + }); +} diff --git a/src/patient-chart/results-summary/test-children-results.component.tsx b/src/patient-chart/results-summary/test-children-results.component.tsx index 287c153..9355200 100644 --- a/src/patient-chart/results-summary/test-children-results.component.tsx +++ b/src/patient-chart/results-summary/test-children-results.component.tsx @@ -66,8 +66,6 @@ const TestResultsChildren: React.FC = ({ })); }, [members]); - console.info("results--->", JSON.stringify(results, null, 2)); - if (members === undefined) { return No Data; } @@ -87,12 +85,14 @@ const TestResultsChildren: React.FC = ({ } return ( - { + {concept?.hiNormal === undefined || concept?.lowNormal === undefined ? ( + "N/A" + ) : (
{concept?.lowNormal} : {concept?.hiNormal} - {concept?.units} + {concept?.units}
- } + )}
); }; diff --git a/src/patient-chart/results-summary/test-print-results-table.component.tsx b/src/patient-chart/results-summary/test-print-results-table.component.tsx index b13ab2b..92d745c 100644 --- a/src/patient-chart/results-summary/test-print-results-table.component.tsx +++ b/src/patient-chart/results-summary/test-print-results-table.component.tsx @@ -11,12 +11,16 @@ import { TableHeader, TableRow, Tile, + TableExpandHeader, + TableExpandRow, + TableExpandedRow, } from "@carbon/react"; import styles from "./results-summary.scss"; import RescendTestResultActionMenu from "./test-results-rescend-action-menu.component"; import { Order } from "../laboratory-order.resource"; import DeleteTestResultActionMenu from "./test-results-delete-action-menu.component"; import { Ob } from "../laboratory-item/view-laboratory-item.resource"; +import TestResultsChildren from "./test-children-results.component"; interface TestOrdersProps { obs: Ob[]; @@ -25,7 +29,10 @@ interface TestOrdersProps { const TestsPrintResults: React.FC = ({ obs }) => { const { t } = useTranslation(); - let columns = [{ id: 1, header: t("order", "Order"), key: "order" }]; + let columns = [ + { id: 1, header: t("order", "Order"), key: "order" }, + { id: 2, header: t("results", "Results"), key: "result" }, + ]; const filteredItems = obs.filter((ob) => ob?.order?.type === "testorder"); @@ -33,16 +40,25 @@ const TestsPrintResults: React.FC = ({ obs }) => { return filteredItems?.map((entry) => ({ ...entry, id: entry.uuid, + members: entry?.groupMembers, order: { content: {entry.display}, }, + result: { + content: --, + }, })); }, [filteredItems]); if (filteredItems?.length >= 0) { return (
- + {({ rows, headers, getHeaderProps, getTableProps, getRowProps }) => ( = ({ obs }) => { > + {headers.map((header) => ( {header.header} @@ -62,13 +79,28 @@ const TestsPrintResults: React.FC = ({ obs }) => { {rows.map((row, index) => { return ( - + {row.cells.map((cell) => ( {cell.value?.content ?? cell.value} ))} - + + {row.isExpanded ? ( + + + + ) : ( + + )} ); })} @@ -80,15 +112,11 @@ const TestsPrintResults: React.FC = ({ obs }) => {

{t( - "noTestOrdersToDisplay", - "No test orders to display" + "noTestResultsToDisplay", + "No test results to display" )}

-

- {t("checkFilters", "Check the filters above")} -

-

{t("or", "or")}

) : null} diff --git a/src/patient-chart/results-summary/test-results-table.component.tsx b/src/patient-chart/results-summary/test-results-table.component.tsx index dd17da4..65384f8 100644 --- a/src/patient-chart/results-summary/test-results-table.component.tsx +++ b/src/patient-chart/results-summary/test-results-table.component.tsx @@ -29,19 +29,21 @@ const TestsResults: React.FC = ({ obs }) => { const { t } = useTranslation(); let columns = [ - { id: 1, header: t("order", "Order"), key: "order" }, + { id: 0, header: t("order", "Order"), key: "order", align: "center" }, { - id: 2, + id: 1, header: t("date", "Date"), key: "date", + align: "center", }, { - id: 3, + id: 2, header: t("result", "Results"), key: "result", + align: "center", }, - { id: 4, header: t("actions", "Actions"), key: "actions" }, + { id: 3, header: t("actions", "Actions"), key: "actions" }, ]; const obsList = obs.filter((ob) => ob?.order?.type === "testorder"); diff --git a/src/queue-list/lab-dialogs/add-to-worklist-dialog.component.tsx b/src/queue-list/lab-dialogs/add-to-worklist-dialog.component.tsx index 5f8cb30..8062e5e 100644 --- a/src/queue-list/lab-dialogs/add-to-worklist-dialog.component.tsx +++ b/src/queue-list/lab-dialogs/add-to-worklist-dialog.component.tsx @@ -28,17 +28,25 @@ import { } from "@openmrs/esm-framework"; import { Renew } from "@carbon/react/icons"; import { + GenerateSpecimenId, + GetOrderByUuid, + UpdateOrder, useQueueRoomLocations, useSpecimenTypes, } from "./add-to-worklist-dialog.resource"; +import { Encounter, Order } from "../../types/patient-queues"; interface AddToWorklistDialogProps { - queueEntry: MappedPatientQueueEntry; + queueId; + encounter: Encounter; + order: Order; closeModal: () => void; } const AddToWorklistDialog: React.FC = ({ - queueEntry, + queueId, + encounter, + order, closeModal, }) => { const { t } = useTranslation(); @@ -51,13 +59,24 @@ const AddToWorklistDialog: React.FC = ({ const [preferred, setPreferred] = useState(false); + const [specimenID, setSpecimenID] = useState(); + const { specimenTypes } = useSpecimenTypes(); + const [orderer, setOrderer] = useState(""); + + const [concept, setConcept] = useState(""); + + const [patient, setPatient] = useState(""); + + const [encounterUuid, setEncounterUuid] = useState(""); + const { queueRoomLocations } = useQueueRoomLocations( sessionUser?.sessionLocation?.uuid ); - const [specimenType, setSpecimenType] = useState(""); + const [specimenType, setSpecimenType] = useState(); + const [selectedNextQueueLocation, setSelectedNextQueueLocation] = useState( queueRoomLocations[0]?.uuid ); @@ -72,105 +91,138 @@ const AddToWorklistDialog: React.FC = ({ } }, [locations, sessionUser]); - const pickLabRequestQueue = useCallback((event) => { + // GetOrderByUuid + GetOrderByUuid(order.uuid).then( + (resp) => { + setOrderer(resp.data?.orderer?.uuid); + setConcept(resp.data?.concept?.uuid); + setPatient(resp.data?.patient?.uuid); + setEncounterUuid(resp.data?.encounter.uuid); + }, + (err) => { + showNotification({ + title: t(`errorGettingOrder', 'Error Getting Order Id`), + kind: "error", + critical: true, + description: err?.message, + }); + } + ); + + const pickLabRequestQueue = async (event) => { event.preventDefault(); - }, []); + // pick lab test + let body = { + sampleId: specimenID, + specimenSourceId: specimenType, + unProcessedOrders: "", + patientQueueId: queueId, + }; + + UpdateOrder(order.uuid, body).then( + () => { + showToast({ + critical: true, + title: t("pickedAnOrder", "Picked an order"), + kind: "success", + description: t( + "pickSuccessfully", + "You have successfully picked an Order" + ), + }); + closeModal(); + }, + (error) => { + showNotification({ + title: t(`errorPicking an order', 'Error Picking an Order`), + kind: "error", + critical: true, + description: error?.message, + }); + } + ); + }; const onChecked = () => { setPreferred(!preferred); }; - const GenerateID = () => { - return ( - - - + const generateId = async (e) => { + e.preventDefault(); + // generate sample Id + GenerateSpecimenId(order.uuid).then( + (resp) => { + setSpecimenID(resp.data.results[0].sampleId); + showToast({ + critical: true, + title: t("generatesampleID", "Generate Sample Id"), + kind: "success", + description: t( + "generateSuccessfully", + "You have successfully generated a Sample Id" + ), + }); + }, + (err) => { + showNotification({ + title: t(`errorGeneratingId', 'Error Generating Sample Id`), + kind: "error", + critical: true, + description: err?.message, + }); + } ); }; - if (queueEntry && Object.keys(queueEntry)?.length > 0) { - return ( -
-
- - -
-

- Currently Picked : {queueEntry.name} -

-
-
-
- {t("specimenID", "Specimen ID")} -
-
-
- -
-
- -
-
+ return ( +
+ + + +
+
+
+
+ {t("specimenID", "Specimen ID")}
-
-
+
-
- {t("specimenType", "Specimen Type")} +
+
-
-
- -
+
+
-
-
+
+
+
= ({ alignContent: "stretch", }} > -
- +
+ {t("specimenType", "Specimen Type")}
- {preferred && ( -
-
- -
-
- )} -
-
-
- - - - - -
- ); - } +
+
+ +
+
+
+
+
+
+ +
+ {preferred && ( +
+
+ +
+
+ )} +
+
+
+ + + + + +
+ ); }; export default AddToWorklistDialog; diff --git a/src/queue-list/lab-dialogs/add-to-worklist-dialog.resource.ts b/src/queue-list/lab-dialogs/add-to-worklist-dialog.resource.ts index 00d27ca..947ad52 100644 --- a/src/queue-list/lab-dialogs/add-to-worklist-dialog.resource.ts +++ b/src/queue-list/lab-dialogs/add-to-worklist-dialog.resource.ts @@ -81,6 +81,7 @@ export interface ParentLocation { display: string; } +// get queue rooms export function useQueueRoomLocations(currentQueueLocation: string) { const apiUrl = `/ws/rest/v1/location/${currentQueueLocation}?v=full`; const { data, error, isLoading } = useSWR<{ data: QueueRoomsResponse }>( @@ -119,8 +120,36 @@ export function useSpecimenTypes() { } // generate specimen id -export function useGenerateSampleID() { - return { - sampleID: {}, - }; +export async function GenerateSpecimenId(uuid: string) { + const abortController = new AbortController(); + return openmrsFetch(`/ws/rest/v1/generatesampleId?uuid=${uuid}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + signal: abortController.signal, + }); +} + +// update Order +export async function UpdateOrder(uuid: string, body: any) { + const abortController = new AbortController(); + return openmrsFetch(`/ws/rest/v1/accessionorder/${uuid}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + signal: abortController.signal, + body: body, + }); +} + +export async function GetOrderByUuid(uuid: string) { + const abortController = new AbortController(); + return openmrsFetch(`/ws/rest/v1/order/${uuid}`, { + headers: { + "Content-Type": "application/json", + }, + signal: abortController.signal, + }); } diff --git a/src/queue-list/lab-tests/lab-tests.component.tsx b/src/queue-list/lab-tests/lab-tests.component.tsx index e0b4431..60d5748 100644 --- a/src/queue-list/lab-tests/lab-tests.component.tsx +++ b/src/queue-list/lab-tests/lab-tests.component.tsx @@ -1,39 +1,121 @@ -import React from "react"; +import React, { useMemo } from "react"; import { useTranslation } from "react-i18next"; -import { useGetLabOrders } from "./lab-tests.resource"; -import { DataTableSkeleton } from "@carbon/react"; +import { + DataTable, + DataTableHeader, + DataTableSkeleton, + Pagination, + Table, + TableBody, + TableCell, + TableContainer, + TableExpandHeader, + TableExpandRow, + TableHead, + TableHeader, + TableRow, + TabPanel, + TableToolbar, + TableToolbarContent, + TableToolbarSearch, + Layer, + Tag, + TableExpandedRow, +} from "@carbon/react"; import { ErrorState } from "@openmrs/esm-framework"; +import { Encounter } from "../../types/patient-queues"; +import styles from "../laboratory-queue.scss"; +import PickLabRequestActionMenu from "../pick-lab-request-menu.component"; -const LabTests = () => { - const { t } = useTranslation(); - - // get lab orders - const { data: labOrders, isLoading, isError } = useGetLabOrders(""); - - if (isLoading) { - return ; - } - - if (isError) { - return ; - } - - // const filteredItems = labOrders.orders.filter( - // (ob) => ob?.order?.type === "testorder" - // ); +interface LabTestsProps { + encounter: Encounter; + queueId: string; +} +const LabTests: React.FC = ({ encounter, queueId }) => { + const { t } = useTranslation(); + // console.info(encounter); let columns = [ + { id: 1, header: t("order", "Order"), key: "order", align: "left" }, { - id: 1, - header: t("date", "Date"), - key: "date", + id: 2, + header: t("orderType", "OrderType"), + key: "orderType", + align: "center", }, - { id: 2, header: t("orderNumber", "Order Number"), key: "orderNumber" }, - { id: 3, header: t("order", "Order"), key: "order" }, - - { id: 5, header: t("actions", "Actions"), key: "actions" }, + { id: 3, header: t("actions", "Actions"), key: "actions", align: "center" }, ]; + + const tableRows = useMemo(() => { + return encounter?.orders?.map((item) => ({ + ...item, + id: item.uuid, + order: { + content: {item.display}, + }, + orderType: { + content: {item.type}, + }, + actions: { + content: ( + true} + order={item} + encounter={encounter} + queueId={queueId} + /> + ), + }, + })); + }, [encounter, queueId]); + + if (!encounter) { + return ( + + ); + } + + return ( +
+
+ + {({ rows, headers, getHeaderProps, getTableProps, getRowProps }) => ( + +
+ + + {headers.map((header) => ( + + {header.header} + + ))} + + + + {rows.map((row, index) => { + return ( + + + {row.cells.map((cell) => ( + + {cell.value?.content ?? cell.value} + + ))} + + + ); + })} + +
+
+ )} +
+
+ ); }; export default LabTests; diff --git a/src/queue-list/lab-tests/lab-tests.resource.ts b/src/queue-list/lab-tests/lab-tests.resource.ts index 4a6324b..f119b9d 100644 --- a/src/queue-list/lab-tests/lab-tests.resource.ts +++ b/src/queue-list/lab-tests/lab-tests.resource.ts @@ -2,7 +2,7 @@ import { FetchResponse, openmrsFetch, useConfig } from "@openmrs/esm-framework"; import useSWR from "swr"; import { EncounterResponse } from "../../patient-chart/laboratory-item/view-laboratory-item.resource"; export function useGetLabOrders(encounterUuid: string) { - const apiUrl = `/ws/rest/v1/encounter/${encounterUuid}`; + const apiUrl = `/ws/rest/v1/encounter/${encounterUuid}?v=full`; const { data, error, isLoading } = useSWR<{ data: EncounterResponse }, Error>( apiUrl, @@ -10,7 +10,7 @@ export function useGetLabOrders(encounterUuid: string) { ); return { - data: data.data ? data.data.orders : "", + labOrders: data?.data ? data?.data?.orders : [], isLoading, isError: error, }; diff --git a/src/queue-list/laboratory-patient-list.component.tsx b/src/queue-list/laboratory-patient-list.component.tsx index 9f7884d..8215e5d 100644 --- a/src/queue-list/laboratory-patient-list.component.tsx +++ b/src/queue-list/laboratory-patient-list.component.tsx @@ -20,6 +20,7 @@ import { Layer, Tag, TableExpandedRow, + Tile, } from "@carbon/react"; import { useTranslation } from "react-i18next"; import { @@ -38,8 +39,6 @@ import { trimVisitNumber, } from "../utils/functions"; import LabTests from "./lab-tests/lab-tests.component"; -import AddToWorklist from "./lab-dialogs/add-to-worklist-dialog.component"; -import PickLabRequestActionMenu from "./pick-lab-request-menu.component"; import { EmptyState } from "@openmrs/esm-patient-common-lib"; // type FilterProps = { @@ -78,12 +77,12 @@ const LaboratoryPatientList: React.FC = () => { { id: 2, header: t("age", "Age"), key: "age" }, { id: 3, header: t("orderedFrom", "Ordered from"), key: "orderedFrom" }, { id: 4, header: t("waitingTime", "Waiting time"), key: "waitingTime" }, - { id: 5, header: t("actions", "Actions"), key: "actions" }, ]; const tableRows = useMemo(() => { return paginatedQueueEntries?.map((entry) => ({ ...entry, + encounter: entry.encounter, visitId: { content: {trimVisitNumber(entry.visitNumber)}, }, @@ -108,14 +107,6 @@ const LaboratoryPatientList: React.FC = () => { ), }, - actions: { - content: ( - true} - /> - ), - }, })); }, [paginatedQueueEntries, t]); @@ -159,7 +150,8 @@ const LaboratoryPatientList: React.FC = () => { if (isLoading) { return ; } - if (patientQueueEntries?.length) { + + if (patientQueueEntries?.length >= 0) { return (
@@ -222,7 +214,10 @@ const LaboratoryPatientList: React.FC = () => { colSpan={headers.length + 2} > <> - + ) : ( @@ -236,6 +231,20 @@ const LaboratoryPatientList: React.FC = () => { })} + {rows.length === 0 ? ( +
+ +
+

+ {t( + "noWorklistsToDisplay", + "No workists orders to display" + )} +

+
+
+
+ ) : null} = () => {
); } - - return ( -
-
- -
- ); }; export default LaboratoryPatientList; diff --git a/src/queue-list/laboratory-patient-list.resource.ts b/src/queue-list/laboratory-patient-list.resource.ts index e16887a..9454344 100644 --- a/src/queue-list/laboratory-patient-list.resource.ts +++ b/src/queue-list/laboratory-patient-list.resource.ts @@ -46,6 +46,7 @@ export function usePatientQueueRequest(apiUrl: string) { name: queue.patient?.person.display, patientUuid: queue.patient?.uuid, priorityComment: queue.priorityComment, + encounter: queue.encounter, priority: queue.priorityComment === "Urgent" ? "Priority" : queue.priorityComment, priorityLevel: queue.priority, @@ -76,7 +77,7 @@ export function usePatientQueueRequest(apiUrl: string) { return { patientQueueEntries: mapppedQueues || [], - patientQueueCount: mapppedQueues?.length, + patientQueueCount: mapppedQueues?.length ?? 0, isLoading, isError: error, isValidating, diff --git a/src/queue-list/laboratory-queue.component.tsx b/src/queue-list/laboratory-queue.component.tsx index 3767278..0d6c59c 100644 --- a/src/queue-list/laboratory-queue.component.tsx +++ b/src/queue-list/laboratory-queue.component.tsx @@ -22,22 +22,26 @@ const LaboratoryQueueList: React.FC = () => { { key: "testedOrders", header: t("testedOrders", "Tests ordered"), - status: "ACTIVE", }, { key: "worklist", header: t("worklist", "Worklist"), - status: "", }, { key: "referredTests", header: t("referredTests", "Referred tests"), - status: "", }, { key: "completedTests", header: t("completedTests", "Completed tests"), - status: "", + }, + { + key: "reviewList", + header: t("reviewList", "Review List"), + }, + { + key: "approveList", + header: t("approveList", "Approval List"), }, ]; diff --git a/src/queue-list/laboratory-queue.scss b/src/queue-list/laboratory-queue.scss index 2d854c6..fbb6275 100644 --- a/src/queue-list/laboratory-queue.scss +++ b/src/queue-list/laboratory-queue.scss @@ -187,4 +187,27 @@ title { th[colspan] td[colspan] > div:first-child { padding: 0 1rem; } +} + +.tileContainer { + background-color: $ui-02; + border-top: 1px solid $ui-03; + padding: 5rem 0; +} + +.tile { + margin: auto; + width: fit-content; +} + +.tileContent { + display: flex; + flex-direction: column; + align-items: center; +} + +.content { + @include type.type-style('heading-compact-02'); + color: $text-02; + margin-bottom: 0.5rem; } \ No newline at end of file diff --git a/src/queue-list/laboratory-tabs.component.tsx b/src/queue-list/laboratory-tabs.component.tsx index 3b770da..bc6d126 100644 --- a/src/queue-list/laboratory-tabs.component.tsx +++ b/src/queue-list/laboratory-tabs.component.tsx @@ -4,6 +4,9 @@ import { useTranslation } from "react-i18next"; import styles from "./laboratory-queue.scss"; import LaboratoryPatientList from "./laboratory-patient-list.component"; import { EmptyState } from "@openmrs/esm-patient-common-lib"; +import WorkList from "../work-list/work-list.component"; +import ReviewList from "../review-list/review-list.component"; +import CompletedList from "../completed-list/completed-list.component"; enum TabTypes { STARRED, @@ -31,19 +34,20 @@ const LaboratoryQueueTabs: React.FC = () => { {t("testedOrders", "Tests ordered")} {t("worklist", "Worklist")} {t("referredTests", "Referred tests")} - {t("completedTests", "Completed tests")} + {t("reviewList", "Review")} + {t("approveList", "Approved")} - +
+
+ +
- +
@@ -58,10 +62,13 @@ const LaboratoryQueueTabs: React.FC = () => {
- + +
+
+ +
+
+
diff --git a/src/queue-list/pick-lab-request-menu.component.tsx b/src/queue-list/pick-lab-request-menu.component.tsx index 8061582..079154b 100644 --- a/src/queue-list/pick-lab-request-menu.component.tsx +++ b/src/queue-list/pick-lab-request-menu.component.tsx @@ -5,23 +5,30 @@ import { showModal } from "@openmrs/esm-framework"; import React, { useCallback } from "react"; import { useTranslation } from "react-i18next"; import { MappedPatientQueueEntry } from "./laboratory-patient-list.resource"; +import { Encounter, Order } from "../types/patient-queues"; interface PickLabRequestActionMenuProps { - queueEntry: MappedPatientQueueEntry; + queueId: string; + encounter: Encounter; + order: Order; closeModal: () => void; } const PickLabRequestActionMenu: React.FC = ({ - queueEntry, + queueId, + encounter, + order, }) => { const { t } = useTranslation(); const launchPickLabRequestQueueModal = useCallback(() => { const dispose = showModal("add-to-worklist-dialog", { closeModal: () => dispose(), - queueEntry, + queueId, + encounter, + order, }); - }, [queueEntry]); + }, [encounter, order, queueId]); return ( diff --git a/src/results/result-form.component.tsx b/src/results/result-form.component.tsx new file mode 100644 index 0000000..1540049 --- /dev/null +++ b/src/results/result-form.component.tsx @@ -0,0 +1,223 @@ +import React, { useMemo, useState } from "react"; +import styles from "./result-form.scss"; +import { + Button, + InlineLoading, + TextInput, + Select, + SelectItem, + ModalBody, + ModalFooter, + ModalHeader, +} from "@carbon/react"; +import { useTranslation } from "react-i18next"; +import { closeOverlay } from "../components/overlay/hook"; +import { + ExtensionSlot, + showNotification, + showToast, + usePatient, +} from "@openmrs/esm-framework"; +import { + UpdateEncounter, + useGetOrderConceptByUuid, +} from "./result-form.resource"; +import { Result } from "../work-list/work-list.resource"; + +interface ResultFormProps { + patientUuid: string; + order: Result; +} + +const ResultForm: React.FC = ({ order, patientUuid }) => { + const { t } = useTranslation(); + + const { patient, isLoading } = usePatient(patientUuid); + + const { concept } = useGetOrderConceptByUuid(order.concept.uuid); + + const [isSubmitting, setIsSubmitting] = useState(false); + + const [inputValues, setInputValues] = useState({}); + + const [selectedOption, setSelectedOption] = useState(); + + const bannerState = useMemo(() => { + if (patient) { + return { + patient, + patientUuid, + hideActionsOverflow: true, + }; + } + }, [patient, patientUuid]); + + // getInput values + const handleInputChange = (memberUuid, value) => { + setInputValues((prevValues) => ({ + ...prevValues, + [memberUuid]: value, + })); + }; + + // create input fields + const Questions = ({ conceptMembers }) => { + const inputFields = useMemo(() => { + return conceptMembers.map((member) => { + let inputField; + + if ( + member.datatype.display === "Text" || + member.datatype.display === "Numeric" + ) { + inputField = ( + handleInputChange(member.uuid, e.target.value)} + /> + ); + } else if (member.datatype.display === "Coded") { + inputField = ( + + ); + } + + return inputField; + }); + }, [conceptMembers]); // Memoize when conceptMembers changes + + return <>{inputFields}; + }; + + const handleSubmit = (e) => { + e.preventDefault(); + // assign value to test + let groupMembers = []; + let obsValue = []; + const ob = { + concept: { uuid: order.concept.uuid }, + status: "FINAL", + order: { uuid: order.uuid }, + groupMembers: groupMembers, + }; + concept.forEach((item) => { + let value; + if ( + item.datatype.display === "Numeric" || + item.datatype.display === "Text" + ) { + value = inputValues[`${item.uuid}`]; + } else if (item.datatype.display === "Coded") { + value = { + uuid: inputValues[`${item.uuid}`], + }; + } + const groupMember = { + concept: { uuid: item.uuid }, + value: value, + status: "FINAL", + order: { uuid: order.uuid }, + }; + groupMembers.push(groupMember); + }); + obsValue.push(ob); + + const payload = { + obs: obsValue, + }; + setIsSubmitting(true); + // update encounter + UpdateEncounter(order.encounter.uuid, payload).then( + () => { + setIsSubmitting(false); + showToast({ + critical: true, + title: t("updateEncounter", "Update Encounter"), + kind: "success", + description: t( + "generateSuccessfully", + "You have successfully encounter with test results" + ), + }); + }, + (err) => { + setIsSubmitting(false); + showNotification({ + title: t( + `errorUpdatingEncounter', 'Error occurred while updating encounter` + ), + kind: "error", + critical: true, + description: err?.message, + }); + } + ); + }; + + return ( + <> +
+ + {isLoading && ( + + )} + {patient && ( + + )} + + {concept?.length > 0 && ( +
+ +
+ )} +
+ + + + + +
+ + ); +}; + +export default ResultForm; diff --git a/src/results/result-form.resource.ts b/src/results/result-form.resource.ts new file mode 100644 index 0000000..dac553e --- /dev/null +++ b/src/results/result-form.resource.ts @@ -0,0 +1,328 @@ +import { openmrsFetch } from "@openmrs/esm-framework"; +import useSWR from "swr"; + +export interface ConceptResponse { + uuid: string; + display: string; + name: Name; + datatype: Datatype; + conceptClass: ConceptClass; + set: boolean; + version: string; + retired: boolean; + names: Name2[]; + descriptions: Description[]; + mappings: Mapping[]; + answers: any[]; + setMembers: SetMember[]; + auditInfo: AuditInfo; + attributes: any[]; + links: Link18[]; + resourceVersion: string; +} + +export interface Name { + display: string; + uuid: string; + name: string; + locale: string; + localePreferred: boolean; + conceptNameType: string; + links: Link[]; + resourceVersion: string; +} + +export interface Link { + rel: string; + uri: string; + resourceAlias: string; +} + +export interface Datatype { + uuid: string; + display: string; + name: string; + description: string; + hl7Abbreviation: string; + retired: boolean; + links: Link2[]; + resourceVersion: string; +} + +export interface Link2 { + rel: string; + uri: string; + resourceAlias: string; +} + +export interface ConceptClass { + uuid: string; + display: string; + name: string; + description: string; + retired: boolean; + links: Link3[]; + resourceVersion: string; +} + +export interface Link3 { + rel: string; + uri: string; + resourceAlias: string; +} + +export interface Name2 { + display: string; + uuid: string; + name: string; + locale: string; + localePreferred: boolean; + conceptNameType?: string; + links: Link4[]; + resourceVersion: string; +} + +export interface Link4 { + rel: string; + uri: string; + resourceAlias: string; +} + +export interface Description { + display: string; + uuid: string; + description: string; + locale: string; + links: Link5[]; + resourceVersion: string; +} + +export interface Link5 { + rel: string; + uri: string; + resourceAlias: string; +} + +export interface Mapping { + display: string; + uuid: string; + conceptReferenceTerm: ConceptReferenceTerm; + conceptMapType: ConceptMapType; + links: Link8[]; + resourceVersion: string; +} + +export interface ConceptReferenceTerm { + uuid: string; + display: string; + links: Link6[]; +} + +export interface Link6 { + rel: string; + uri: string; + resourceAlias: string; +} + +export interface ConceptMapType { + uuid: string; + display: string; + links: Link7[]; +} + +export interface Link7 { + rel: string; + uri: string; + resourceAlias: string; +} + +export interface Link8 { + rel: string; + uri: string; + resourceAlias: string; +} + +export interface SetMember { + uuid: string; + display: string; + name: Name3; + datatype: Datatype2; + conceptClass: ConceptClass2; + set: boolean; + version: string; + retired: boolean; + names: Name4[]; + descriptions: any[]; + mappings: Mapping2[]; + answers: Answer[]; + setMembers: any[]; + attributes: any[]; + links: Link15[]; + resourceVersion: string; +} + +export interface Name3 { + display: string; + uuid: string; + name: string; + locale: string; + localePreferred: boolean; + conceptNameType: string; + links: Link9[]; + resourceVersion: string; +} + +export interface Link9 { + rel: string; + uri: string; + resourceAlias: string; +} + +export interface Datatype2 { + uuid: string; + display: string; + links: Link10[]; +} + +export interface Link10 { + rel: string; + uri: string; + resourceAlias: string; +} + +export interface ConceptClass2 { + uuid: string; + display: string; + links: Link11[]; +} + +export interface Link11 { + rel: string; + uri: string; + resourceAlias: string; +} + +export interface Name4 { + uuid: string; + display: string; + links: Link12[]; +} + +export interface Link12 { + rel: string; + uri: string; + resourceAlias: string; +} + +export interface Mapping2 { + uuid: string; + display: string; + links: Link13[]; +} + +export interface Link13 { + rel: string; + uri: string; + resourceAlias: string; +} + +export interface Answer { + uuid: string; + display: string; + links: Link14[]; +} + +export interface Link14 { + rel: string; + uri: string; + resourceAlias: string; +} + +export interface Link15 { + rel: string; + uri: string; + resourceAlias: string; +} + +export interface AuditInfo { + creator: Creator; + dateCreated: string; + changedBy: ChangedBy; + dateChanged: string; +} + +export interface Creator { + uuid: string; + display: string; + links: Link16[]; +} + +export interface Link16 { + rel: string; + uri: string; + resourceAlias: string; +} + +export interface ChangedBy { + uuid: string; + display: string; + links: Link17[]; +} + +export interface Link17 { + rel: string; + uri: string; + resourceAlias: string; +} + +export interface Link18 { + rel: string; + uri: string; + resourceAlias: string; +} + +export interface ObPayload { + concept: string; + order: string; + value: string | number; + status: string; +} + +// get order concept +export async function GetOrderConceptByUuid(uuid: string) { + const abortController = new AbortController(); + return openmrsFetch(`/ws/rest/v1/concept/${uuid}?v=full`, { + headers: { + "Content-Type": "application/json", + }, + signal: abortController.signal, + }); +} + +export function useGetOrderConceptByUuid(uuid: string) { + const apiUrl = `/ws/rest/v1/concept/${uuid}?v=full`; + const { data, error, isLoading, isValidating, mutate } = useSWR< + { data: ConceptResponse }, + Error + >(apiUrl, openmrsFetch); + return { + concept: data?.data ? data?.data.setMembers : [], + isLoading, + isError: error, + isValidating, + mutate, + }; +} + +// create observation +export async function UpdateEncounter(uuid: string, payload: any) { + const abortController = new AbortController(); + return openmrsFetch(`/ws/rest/v1/encounter/${uuid}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + signal: abortController.signal, + body: payload, + }); +} diff --git a/src/results/result-form.scss b/src/results/result-form.scss new file mode 100644 index 0000000..12aca1b --- /dev/null +++ b/src/results/result-form.scss @@ -0,0 +1,19 @@ +@use '@carbon/styles/scss/spacing'; +@use '@carbon/styles/scss/type'; + + +section { + margin: spacing.$spacing-03; +} + +.sectionTitle { + margin-bottom: spacing.$spacing-04; +} + +.modalBody { + padding-bottom: spacing.$spacing-05; +} + +.textInput { + margin: 5px; +} \ No newline at end of file diff --git a/src/review-list/dialog/review-item.component.tsx b/src/review-list/dialog/review-item.component.tsx new file mode 100644 index 0000000..763dcfd --- /dev/null +++ b/src/review-list/dialog/review-item.component.tsx @@ -0,0 +1,237 @@ +import React, { useMemo, useState } from "react"; +import { + Button, + Form, + ModalBody, + ModalFooter, + ModalHeader, + InlineLoading, + Checkbox, +} from "@carbon/react"; +import { useTranslation } from "react-i18next"; +import { useGetEncounterById } from "../../patient-chart/laboratory-item/view-laboratory-item.resource"; +import styles from "../review-list.scss"; +import { GroupMember } from "../../patient-chart/laboratory-order.resource"; +import { useGetConceptById } from "../../patient-chart/results-summary/results-summary.resource"; + +interface ReviewItemDialogProps { + encounterUuid: string; + closeModal: () => void; +} + +interface ResultsRowProps { + groupMembers: GroupMember[]; +} + +interface ValueUnitsProps { + conceptUuid: string; +} + +const ReviewItem: React.FC = ({ + encounterUuid, + closeModal, +}) => { + const { t } = useTranslation(); + + const { encounter, isLoading, isError } = useGetEncounterById(encounterUuid); + + const testsOrder = useMemo(() => { + return encounter?.obs.filter((item) => item?.order?.type === "testorder"); + }, [encounter?.obs]); + + const filteredGroupedResults = useMemo(() => { + let groupedResults = []; + + testsOrder?.forEach((element) => { + groupedResults[element.order.display] = element; + }); + return groupedResults; + }, [testsOrder]); + + const [checkedItems, setCheckedItems] = useState({}); + + const handleCheckboxChange = (test, groupMembers) => { + setCheckedItems((prevCheckedItems) => ({ + ...prevCheckedItems, + [test]: { + groupMembers, + }, + })); + }; + + // get Units + const ValueUnits: React.FC = ({ conceptUuid }) => { + const { + concept: concept, + isLoading, + isError, + } = useGetConceptById(conceptUuid); + if (isLoading) { + return ; + } + if (isError) { + return Error; + } + return {concept?.units}; + }; + + // get Reference Range + const ReferenceRange: React.FC = ({ conceptUuid }) => { + const { + concept: concept, + isLoading, + isError, + } = useGetConceptById(conceptUuid); + if (isLoading) { + return ; + } + if (isError) { + return Error; + } + return ( + <> + {concept?.hiNormal === undefined || concept?.lowNormal === undefined ? ( + "N/A" + ) : ( +
+ {concept?.lowNormal ? concept?.lowNormal : "--"} :{" "} + {concept?.hiNormal ? concept?.hiNormal : "--"} +
+ )} + + ); + }; + + const RowTest: React.FC = ({ groupMembers }) => { + return ( + <> + {groupMembers?.map((element, index) => { + return ( + + {typeof element.value === "number" ? ( + <> + {element?.concept.display} + + {element?.value} + + + { + + } + + + + { + + } + + + ) : typeof element.value === "object" ? ( + <> + {element?.concept.display} + + {element?.value.display} + + + { + + } + + + + { + + } + + + ) : ( + {element?.display} + )} + + ); + })} + + ); + }; + + return ( +
+
+ + + {isLoading && ( + + )} +
+ + + {Object.keys(filteredGroupedResults).map((test, index) => ( + + + handleCheckboxChange( + test, + filteredGroupedResults[test].groupMembers + ) + } + labelText={test} + id={`test-${test}`} + checked={checkedItems[test] || false} + /> + +
+ + + + + + + + + + + +
TestsResultReference RangeUnits
+ + ))} + + +
+
+ + + + + +
+ ); +}; +export default ReviewItem; diff --git a/src/review-list/dialog/review-item.resource.ts b/src/review-list/dialog/review-item.resource.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/review-list/dialog/review-item.scss b/src/review-list/dialog/review-item.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/review-list/review-list.component.tsx b/src/review-list/review-list.component.tsx new file mode 100644 index 0000000..f7fccab --- /dev/null +++ b/src/review-list/review-list.component.tsx @@ -0,0 +1,254 @@ +import React, { useCallback, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useGetOrdersWorklist } from "../work-list/work-list.resource"; +import { ErrorState, showModal, usePagination } from "@openmrs/esm-framework"; +import { + DataTable, + DataTableSkeleton, + Pagination, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableHeader, + TableRow, + TableToolbar, + TableToolbarContent, + TableToolbarSearch, + Layer, + Tile, + DatePicker, + DatePickerInput, + Select, + SelectItem, + Button, + Tag, +} from "@carbon/react"; + +import styles from "./review-list.scss"; +import { Add } from "@carbon/react/icons"; +import { Ob } from "../patient-chart/laboratory-order.resource"; +import { Encounter } from "../types"; +import { getStatusColor } from "../utils/functions"; + +interface ReviewlistProps { + fulfillerStatus: string; +} +interface ApproveResultMenuProps { + encounterUuid: string; +} + +const ApproveTestMenu: React.FC = ({ + encounterUuid, +}) => { + const { t } = useTranslation(); + const launchReviewItemModal = useCallback(() => { + const dispose = showModal("review-item-dialog", { + encounterUuid, + closeModal: () => dispose(), + }); + }, [encounterUuid]); + + return ( + + ); +}; + +const ReviewList: React.FC = ({ fulfillerStatus }) => { + const { t } = useTranslation(); + + const [activatedOnOrAfterDate, setActivatedOnOrAfterDate] = useState(""); + + const { workListEntries, isLoading } = useGetOrdersWorklist( + activatedOnOrAfterDate, + fulfillerStatus + ); + + const pageSizes = [10, 20, 30, 40, 50]; + const [page, setPage] = useState(1); + const [currentPageSize, setPageSize] = useState(10); + const [nextOffSet, setNextOffSet] = useState(0); + + const { + goTo, + results: paginatedWorkListEntries, + currentPage, + } = usePagination(workListEntries, currentPageSize); + + // get picked orders + let columns = [ + { id: 0, header: t("orderNumber", "Order Number"), key: "orderNumber" }, + { + id: 1, + header: t("accessionNumber", "Accession Number"), + key: "accessionNumber", + }, + { id: 2, header: t("test", "Test"), key: "test" }, + { id: 3, header: t("action", "Action"), key: "action" }, + { id: 4, header: t("status", "Status"), key: "status" }, + { id: 5, header: t("orderer", "Orderer"), key: "orderer" }, + { id: 6, header: t("orderType", "Order Type"), key: "orderType" }, + { id: 7, header: t("urgency", "Urgency"), key: "urgency" }, + ]; + + const tableRows = useMemo(() => { + return paginatedWorkListEntries?.map((entry, index) => ({ + ...entry, + id: entry.uuid, + orderNumber: { content: {entry.orderNumber} }, + accessionNumber: { content: {entry.accessionNumber} }, + test: { content: {entry.concept.display} }, + action: { content: {entry.action} }, + status: { + content: ( + <> + + + {entry.fulfillerStatus} + + + + ), + }, + orderer: { content: {entry.orderer.display} }, + orderType: { content: {entry.orderType.display} }, + urgency: { content: {entry.urgency} }, + })); + }, [paginatedWorkListEntries]); + + if (isLoading) { + return ; + } + if (paginatedWorkListEntries?.length >= 0) { + return ( +
+
+ + {({ + rows, + headers, + getHeaderProps, + getTableProps, + getRowProps, + onInputChange, + }) => ( + + + + + + { + setActivatedOnOrAfterDate(event.target.value); + }} + type="date" + value={activatedOnOrAfterDate} + /> + + + + + + + + + + + {headers.map((header) => ( + + {header.header} + + ))} + + + + {rows.map((row, index) => { + return ( + + + {row.cells.map((cell) => ( + + {cell.value?.content ?? cell.value} + + ))} + + + + + + ); + })} + +
+ {rows.length === 0 ? ( +
+ +
+

+ {t( + "noReviewListToDisplay", + "No review list to display" + )} +

+
+
+
+ ) : null} + { + if (pageSize !== currentPageSize) { + setPageSize(pageSize); + } + if (page !== currentPage) { + goTo(page); + } + }} + /> +
+ )} +
+
+ ); + } +}; + +export default ReviewList; diff --git a/src/review-list/review-list.resource.ts b/src/review-list/review-list.resource.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/review-list/review-list.scss b/src/review-list/review-list.scss new file mode 100644 index 0000000..26b8c00 --- /dev/null +++ b/src/review-list/review-list.scss @@ -0,0 +1,189 @@ +@use '@carbon/styles/scss/spacing'; +@use '@carbon/styles/scss/type'; +@import "~@openmrs/esm-styleguide/src/vars"; + +title { + width: 6.938rem; + height: 1.75rem; + margin: 0.438rem 22.875rem 0.813rem 0; + font-family: IBMPlexSans; + font-size: 1.25rem; + font-weight: normal; + font-stretch: normal; + font-style: normal; + line-height: 1.4; + letter-spacing: normal; +} + +.link { + text-decoration: none; +} + + +.orderTabs { + grid-column: 'span 2'; + padding: 0 spacing.$spacing-05; +} + +.newListButton { + width: fit-content; + justify-self: end; + align-self: center; +} + +.hiddenTabsContent, +.tabs .hiddenTabsContent { + display: none; +} + +.patientListTableContainer { + grid-row: 3 / 4; + grid-column: 1 / 2; + height: 100%; + margin: 0.5rem spacing.$spacing-05; + background-color: $ui-01; + border: 0.5px solid #e0e0e0; + + :global(.cds--data-table-container) { + padding-top: 0 !important; + } + + tbody>tr>:nth-child(2) { + white-space: nowrap; + } + + :global(.cds--data-table td) { + height: unset !important; + } + + :global(.cds--data-table--zebra) tbody tr[data-parent-row]:nth-child(4n + 1) td { + background-color: $ui-02; + border-bottom: 1px solid $ui-03; + border-top: 1px solid $ui-03; + } + + :global(.cds--data-table--zebra) tbody tr[data-parent-row]:nth-child(4n + 3) td { + background-color: $ui-01; + border-bottom: 1px solid $ui-03; + } +} + +.tableContainer { + background-color: $ui-01; + margin: 0 spacing.$spacing-05; + padding: 0; + + a { + text-decoration: none; + } + + th { + color: $text-02; + } + + :global(.cds--data-table) { + background-color: $ui-03; + } + + .toolbarContent { + height: spacing.$spacing-07; + margin-bottom: spacing.$spacing-02; + } +} + +.activePatientsTable tr:last-of-type { + td { + border-bottom: none; + } +} +.headerBtnContainer { + background-color: $ui-background; + padding: spacing.$spacing-05; + text-align: right; +} + +.searchContainer { + display: flex; + align-items: center; + flex-direction: row-reverse; + padding-top: 0.5rem; + + :global(.cds--search-magnifier-icon) { + z-index: 0 !important; + } + + input { + background-color: #fff; + } +} + +.addOrderBtn { + width: 10rem !important; + padding: 0.5rem !important; + margin-left: 1rem; + margin-right: 1rem; +} + +.patientSearch { + width: 25rem; + border-bottom-color: $ui-03; +} + +.locationFilter { + width: 25rem; +} + +.search { + width: 100%; + max-width: 16rem; + background-color: $ui-02; + border-bottom-color: $ui-03; +} + +.container { + background-color: $ui-01; +} + +.tileContainer { + background-color: $ui-02; + border-top: 1px solid $ui-03; + padding: 5rem 0; +} + +.tile { + margin: auto; + width: fit-content; +} + +.tileContent { + display: flex; + flex-direction: column; + align-items: center; +} +.content { + @include type.type-style('heading-compact-02'); + color: $text-02; + margin-bottom: 0.5rem; +} + +.section { + table { + font-family: Arial, sans-serif; + border-collapse: collapse; + width: 100%; + } + + td, th { + border: 1px solid #000; + text-align: left; + font-size: 8px; + padding: 8px; + width: 80px; + } + + th { + background-color: #f2f2f2; + } + + +} \ No newline at end of file diff --git a/src/root.scss b/src/root.scss index 49d113a..e2f3485 100644 --- a/src/root.scss +++ b/src/root.scss @@ -1,15 +1,50 @@ @use '@carbon/styles/scss/spacing'; @use '@carbon/styles/scss/type'; +@import '~@openmrs/esm-styleguide/src/vars'; -.container { - padding: spacing.$spacing-07; +.productiveHeading01 { + @include type.type-style("heading-01"); } -.welcome { - @include type.type-style('heading-04'); - margin: spacing.$spacing-05 0; +.productiveHeading02 { + @include type.type-style("heading-02"); } -.explainer { - margin-bottom: 2rem; +.productiveHeading03 { + @include type.type-style("heading-03"); } + +.productiveHeading04 { + @include type.type-style("heading-04"); +} + +.bodyLong01 { + @include type.type-style("body-01"); +} + +.bodyShort01 { + @include type.type-style("body-compact-01"); +} + +.bodyShort02 { + @include type.type-style("body-compact-02"); +} + +.text02 { + color: $text-02; +} + +.label01 { + @include type.type-style("label-01") +} + +.modal { + position: fixed; + width: 100vw; + height: 100vh; + display: grid; + background-color: rgba(0, 0, 0, 0.5); + z-index: 9000; + justify-items: center; + align-items: center; +} \ No newline at end of file diff --git a/src/routes.json b/src/routes.json index a1b1d28..3220704 100644 --- a/src/routes.json +++ b/src/routes.json @@ -59,6 +59,10 @@ { "name":"send-email-dialog", "component": "sendEmailDialog" + }, + { + "name" : "review-item-dialog", + "component": "reviewItemDialog" } ] } diff --git a/src/summary-tiles/laboratory-summary-tiles.component.tsx b/src/summary-tiles/laboratory-summary-tiles.component.tsx index 37e4d2f..788ca42 100644 --- a/src/summary-tiles/laboratory-summary-tiles.component.tsx +++ b/src/summary-tiles/laboratory-summary-tiles.component.tsx @@ -1,39 +1,51 @@ import React from "react"; import { useTranslation } from "react-i18next"; -import { DataTableSkeleton } from "@carbon/react"; -import { useMetrics } from "./laboratory-summary.resource"; +import { useLabTestsStats, useMetrics } from "./laboratory-summary.resource"; import SummaryTile from "./summary-tile.component"; import styles from "./laboratory-summary-tiles.scss"; +import { useSession } from "@openmrs/esm-framework"; +import { usePatientQueuesList } from "../queue-list/laboratory-patient-list.resource"; const LaboratorySummaryTiles: React.FC = () => { const { t } = useTranslation(); - const { metrics, isError, isLoading } = useMetrics(); - if (isLoading || isError) { - return ; - } + const session = useSession(); + + // get tests ordered + const { patientQueueCount } = usePatientQueuesList( + session?.sessionLocation?.uuid, + "pending" + ); + + // get worklists + const { count: worklistCount } = useLabTestsStats("IN_PROGRESS"); + + // get refered lists + + // get approved + const { count: completedCount } = useLabTestsStats("COMPLETED"); return ( <>
diff --git a/src/summary-tiles/laboratory-summary.resource.tsx b/src/summary-tiles/laboratory-summary.resource.tsx index f694224..e52657b 100644 --- a/src/summary-tiles/laboratory-summary.resource.tsx +++ b/src/summary-tiles/laboratory-summary.resource.tsx @@ -1,6 +1,7 @@ import useSWR from "swr"; import useSWRImmutable from "swr/immutable"; import { FetchResponse, openmrsFetch } from "@openmrs/esm-framework"; +import { Result } from "../work-list/work-list.resource"; export function useMetrics() { const metrics = { @@ -33,29 +34,17 @@ export function useServices() { }; } -// test ordered -export function useOrderedTests() { - return { - count: 0, - }; -} // worklist -export function useWorklistsStats() { - return { - count: 0, - }; -} - -// referred tests -export function useReferredTestsStats() { - return { - count: 0, - }; -} - -// results -export function useResultsStats() { +export function useLabTestsStats(fulfillerStatus: string) { + const apiUrl = `/ws/rest/v1/order?orderTypes=52a447d3-a64a-11e3-9aeb-50e549534c5e&isStopped=false&fulfillerStatus=${fulfillerStatus}&v=full + `; + const { data, error, isLoading } = useSWR< + { data: { results: Array } }, + Error + >(apiUrl, openmrsFetch); return { - count: 0, + count: data?.data ? data.data.results.length : 0, + isLoading, + isError: error, }; } diff --git a/src/summary-tiles/summary-tile.component.tsx b/src/summary-tiles/summary-tile.component.tsx index 4170256..545a548 100644 --- a/src/summary-tiles/summary-tile.component.tsx +++ b/src/summary-tiles/summary-tile.component.tsx @@ -26,14 +26,15 @@ const SummaryTile: React.FC = ({ {children} +
diff --git a/src/types/patient-queues.ts b/src/types/patient-queues.ts index 8a74b2a..1e85cae 100644 --- a/src/types/patient-queues.ts +++ b/src/types/patient-queues.ts @@ -39,6 +39,7 @@ export interface PatientQueue { }; voided: boolean; }; + encounter: Encounter; provider: { uuid: string; display: string; @@ -113,3 +114,76 @@ export interface UuidDisplay { uuid: string; display: string; } + +export interface Encounter { + uuid: string; + display: string; + encounterDatetime: string; + patient: Patient; + location: Location; + form: Form; + encounterType: EncounterType; + obs: Ob[]; + orders: Order[]; + voided: boolean; + visit: Visit; + encounterProviders: EncounterProvider[]; + diagnoses: any[]; + links: Link[]; + resourceVersion: string; +} + +export interface Patient { + uuid: string; + display: string; + links: Link[]; +} + +export interface Link { + rel: string; + uri: string; + resourceAlias: string; +} + +export interface Location { + uuid: string; + display: string; + links: Link[]; +} + +export interface Form { + uuid: string; + display: string; + links: Link[]; +} + +export interface EncounterType { + uuid: string; + display: string; + links: Link[]; +} + +export interface Ob { + uuid: string; + display: string; + links: Link[]; +} + +export interface Order { + uuid: string; + display: string; + links: Link[]; + type: string; +} + +export interface Visit { + uuid: string; + display: string; + links: Link[]; +} + +export interface EncounterProvider { + uuid: string; + display: string; + links: Link[]; +} diff --git a/src/utils/functions.ts b/src/utils/functions.ts index 067e49d..2c63a10 100644 --- a/src/utils/functions.ts +++ b/src/utils/functions.ts @@ -1,3 +1,6 @@ +import { openmrsFetch } from "@openmrs/esm-framework"; +import useSWR from "swr"; + export const trimVisitNumber = (visitNumber: string) => { if (!visitNumber) { return; @@ -35,3 +38,209 @@ export const getTagColor = (waitTime: string) => { return "red"; } }; + +export const getStatusColor = (fulfillerStatus: string) => { + if (fulfillerStatus === "COMPLETED") { + return "green"; + } else if (fulfillerStatus === "IN_PROGRESS") { + return "orange"; + } else { + return "red"; + } +}; +export interface PatientResource { + uuid: string; + display: string; + identifiers: Identifier[]; + person: Person; + voided: boolean; + auditInfo: AuditInfo; + links: Link[]; + resourceVersion: string; +} + +export interface Identifier { + display: string; + uuid: string; + identifier: string; + identifierType: IdentifierType; + location: Location; + preferred: boolean; + voided: boolean; + links: Link[]; + resourceVersion: string; +} + +export interface IdentifierType { + uuid: string; + display: string; + links: Link[]; +} + +export interface Link { + rel: string; + uri: string; + resourceAlias: string; +} + +export interface Location { + uuid: string; + display: string; + links: Link[]; +} + +export interface Person { + uuid: string; + display: string; + gender: string; + age: number; + birthdate: string; + birthdateEstimated: boolean; + dead: boolean; + deathDate: any; + causeOfDeath: any; + preferredName: PreferredName; + preferredAddress: PreferredAddress; + names: Name[]; + addresses: Address[]; + attributes: Attribute[]; + voided: boolean; + auditInfo: AuditInfo; + birthtime: any; + deathdateEstimated: boolean; + causeOfDeathNonCoded: any; + links: Link[]; + resourceVersion: string; +} + +export interface PreferredName { + display: string; + uuid: string; + givenName: string; + middleName: string; + familyName: string; + familyName2: any; + voided: boolean; + links: Link[]; + resourceVersion: string; +} + +export interface PreferredAddress { + display: any; + uuid: string; + preferred: boolean; + address1: any; + address2: any; + cityVillage: any; + stateProvince: string; + country: string; + postalCode: any; + countyDistrict: string; + address3: string; + address4: string; + address5: string; + address6: any; + startDate: any; + endDate: any; + latitude: any; + longitude: any; + voided: boolean; + address7: any; + address8: any; + address9: any; + address10: any; + address11: any; + address12: any; + address13: any; + address14: any; + address15: any; + links: Link[]; + resourceVersion: string; +} + +export interface Name { + display: string; + uuid: string; + givenName: string; + middleName: string; + familyName: string; + familyName2: any; + voided: boolean; + links: Link[]; + resourceVersion: string; +} + +export interface Address { + display: any; + uuid: string; + preferred: boolean; + address1: any; + address2: any; + cityVillage: any; + stateProvince: string; + country: string; + postalCode: any; + countyDistrict: string; + address3: string; + address4: string; + address5: string; + address6: any; + startDate: any; + endDate: any; + latitude: any; + longitude: any; + voided: boolean; + address7: any; + address8: any; + address9: any; + address10: any; + address11: any; + address12: any; + address13: any; + address14: any; + address15: any; + links: Link[]; + resourceVersion: string; +} + +export interface Attribute { + display: string; + uuid: string; + value: string; + attributeType: AttributeType; + voided: boolean; + links: Link[]; + resourceVersion: string; +} + +export interface AttributeType { + uuid: string; + display: string; + links: Link[]; +} + +export interface AuditInfo { + creator: Creator; + dateCreated: string; + changedBy: any; + dateChanged: any; +} + +export interface Creator { + uuid: string; + display: string; + links: Link[]; +} + +export function useGetPatientByUuid(uuid: string) { + const apiUrl = `/ws/rest/v1/patient/${uuid}?v=full`; + const { data, error, isLoading } = useSWR<{ data: PatientResource }, Error>( + apiUrl, + openmrsFetch + ); + return { + patient: data?.data, + isLoading, + isError: error, + }; +} diff --git a/src/work-list/work-list.component.tsx b/src/work-list/work-list.component.tsx new file mode 100644 index 0000000..beb813c --- /dev/null +++ b/src/work-list/work-list.component.tsx @@ -0,0 +1,260 @@ +import React, { useState, useMemo, AnchorHTMLAttributes } from "react"; +import { useTranslation } from "react-i18next"; +import { EmptyState, ErrorState } from "@openmrs/esm-patient-common-lib"; +import { Microscope } from "@carbon/react/icons"; + +import { + DataTable, + DataTableHeader, + DataTableSkeleton, + Pagination, + Table, + TableBody, + TableCell, + TableContainer, + TableExpandHeader, + TableExpandRow, + TableHead, + TableHeader, + TableRow, + TabPanel, + TableToolbar, + TableToolbarContent, + TableToolbarSearch, + Layer, + Tag, + TableExpandedRow, + Button, + Tile, + DatePicker, + DatePickerInput, + Select, + SelectItem, +} from "@carbon/react"; +import { Result, useGetOrdersWorklist } from "./work-list.resource"; +import styles from "./work-list.scss"; +import { usePagination } from "@openmrs/esm-framework"; +import { launchOverlay } from "../components/overlay/hook"; +import ResultForm from "../results/result-form.component"; +import { getStatusColor } from "../utils/functions"; + +interface WorklistProps { + fulfillerStatus: string; +} + +interface ResultsOrderProps { + order: Result; + patientUuid: string; +} + +const WorkList: React.FC = ({ fulfillerStatus }) => { + const { t } = useTranslation(); + + const [activatedOnOrAfterDate, setActivatedOnOrAfterDate] = useState(""); + + const { workListEntries, isLoading } = useGetOrdersWorklist( + activatedOnOrAfterDate, + fulfillerStatus + ); + + const pageSizes = [10, 20, 30, 40, 50]; + const [page, setPage] = useState(1); + const [currentPageSize, setPageSize] = useState(10); + const [nextOffSet, setNextOffSet] = useState(0); + + const { + goTo, + results: paginatedWorkListEntries, + currentPage, + } = usePagination(workListEntries, currentPageSize); + + // get picked orders + let columns = [ + { id: 0, header: t("orderNumber", "Order Number"), key: "orderNumber" }, + { + id: 1, + header: t("accessionNumber", "Accession Number"), + key: "accessionNumber", + }, + { id: 2, header: t("test", "Test"), key: "test" }, + { id: 3, header: t("action", "Action"), key: "action" }, + { id: 4, header: t("status", "Status"), key: "status" }, + { id: 5, header: t("orderer", "Orderer"), key: "orderer" }, + { id: 6, header: t("orderType", "Order Type"), key: "orderType" }, + { id: 7, header: t("urgency", "Urgency"), key: "urgency" }, + { id: 8, header: t("actions", "Actions"), key: "actions" }, + ]; + + const ResultsOrder: React.FC = ({ + order, + patientUuid, + }) => { + return ( +