diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..2db271f --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,18 @@ +## Requirements + +- [ ] This PR has a title that briefly describes the work done including the ticket number. If there is a ticket, make sure your PR title includes a [conventional commit](https://o3-dev.docs.openmrs.org/#/getting_started/contributing?id=your-pr-title-should-indicate-the-type-of-change-it-is) label. See existing PR titles for inspiration. +- [ ] My work conforms to the [OpenMRS 3.0 Styleguide](https://om.rs/styleguide) and [design documentation](https://zeroheight.com/23a080e38/p/880723-introduction). +- [ ] My work includes tests or is validated by existing tests. + +## Summary + + +## Screenshots + + +## Related Issue + + + +## Other + diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 6abf1bb..bf8b627 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -9,7 +9,7 @@ on: types: - created env: - ESM_NAME: "@ugandaemr/openmrs-esm-laboratory-app" + ESM_NAME: "@openmrs/esm-laboratory-app" JS_NAME: "openmrs-esm-laboratory-app.js" jobs: diff --git a/README.md b/README.md index 396a240..cd2465e 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,21 @@ A frontend module for managing laboratory requests and queues built on OpenMRS 3.x For more information, please refer to the -[OpenMRS 3.x Frontend Documentation](https://openmrs.github.io/openmrs-esm-core/#/). +[OpenMRS 3.x Frontend Documentation](https://o3-docs.openmrs.org/). + +### Dashboard + + + +### Adding Results + + # Getting Started ```sh # Clone the repository -git clone git@github.com:METS-Programme/openmrs-esm-laboratory.git +git clone git@github.com:openmrs/openmrs-esm-laboratory.git # to install dependencies yarn diff --git a/assets/screenshots/labs_enter_results.png b/assets/screenshots/labs_enter_results.png new file mode 100644 index 0000000..5498121 Binary files /dev/null and b/assets/screenshots/labs_enter_results.png differ diff --git a/assets/screenshots/labs_general_dashboard.png b/assets/screenshots/labs_general_dashboard.png new file mode 100644 index 0000000..a28824d Binary files /dev/null and b/assets/screenshots/labs_general_dashboard.png differ diff --git a/package.json b/package.json index 23321f7..0121fe4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "@ugandaemr/openmrs-esm-laboratory-app", - "version": "1.0.1", + "name": "@openmrs/esm-laboratory-app", + "version": "1.0.0", "license": "MPL-2.0", "description": "Laboratory microfrontend for OpenMRS 3.x", "browser": "dist/openmrs-esm-laboratory-app.js", @@ -35,19 +35,22 @@ ], "repository": { "type": "git", - "url": "git+https://github.com/METS-Programme/openmrs-esm-laboratory.git" + "url": "git+https://github.com/openmrs/openmrs-esm-laboratory.git" }, - "homepage": "https://github.com/METS-Programme/openmrs-esm-laboratory#readme", + "homepage": "https://github.com/openmrs/openmrs-esm-laboratory#readme", "publishConfig": { "access": "public" }, "bugs": { - "url": "https://github.com/METS-Programme/openmrs-esm-laboratory/issues" + "url": "https://github.com/openmrs/openmrs-esm-laboratory/issues" }, "dependencies": { "@carbon/react": "^1.14.0", + "@hookform/resolvers": "^3.3.4", "lodash-es": "^4.17.21", - "react-to-print": "^2.14.15" + "react-hook-form": "^7.49.3", + "react-to-print": "^2.14.15", + "zod": "^3.22.4" }, "peerDependencies": { "@openmrs/esm-framework": "*", diff --git a/src/completed-list/completed-list.component.tsx b/src/completed-list/completed-list.component.tsx index aacd4de..b6dd36c 100644 --- a/src/completed-list/completed-list.component.tsx +++ b/src/completed-list/completed-list.component.tsx @@ -34,11 +34,88 @@ import { import styles from "./completed-list.scss"; import { getStatusColor } from "../utils/functions"; -interface CompletedlistProps { +interface CompletedListProps { fulfillerStatus: string; } -const CompletedList: React.FC = ({ fulfillerStatus }) => { +interface TableRowProps { + entry: { + uuid: string; + orderNumber: string; + accessionNumber: string; + concept: { display: string }; + action: string; + fulfillerStatus: string; + orderer: { display: string }; + urgency: string; + dateActivated: string; + patient: { display: string }; + }; +} + +const StatusTag: React.FC<{ fulfillerStatus: string }> = ({ + fulfillerStatus, +}) => { + return ( + + + {fulfillerStatus} + + + ); +}; + +const CustomTableRow: React.FC = ({ entry }) => { + const { + uuid, + orderNumber, + accessionNumber, + concept, + action, + fulfillerStatus, + orderer, + urgency, + dateActivated, + patient, + } = entry; + + return ( + + + {formatDate(parseDate(dateActivated))} + + + {orderNumber} + + + {patient.display.split("-")[1]} + + + {accessionNumber} + + + {concept.display} + + + {action} + + + + + + {orderer.display} + + + {urgency} + + + ); +}; + +const CompletedList: React.FC = ({ fulfillerStatus }) => { const { t } = useTranslation(); const [activatedOnOrAfterDate, setActivatedOnOrAfterDate] = useState(""); @@ -59,173 +136,49 @@ const CompletedList: React.FC = ({ fulfillerStatus }) => { currentPage, } = usePagination(workListEntries, currentPageSize); - // get picked orders - let columns = [ + const tableColumns = [ { id: 0, header: t("date", "Date"), key: "date" }, - { id: 1, header: t("orderNumber", "Order Number"), key: "orderNumber" }, + { id: 2, header: t("patient", "Patient"), key: "patient" }, { - id: 2, + id: 3, header: t("accessionNumber", "Accession Number"), key: "accessionNumber", }, - { id: 3, header: t("test", "Test"), key: "test" }, - { id: 4, header: t("action", "Action"), key: "action" }, - { id: 5, header: t("status", "Status"), key: "status" }, - { id: 6, header: t("orderer", "Orderer"), key: "orderer" }, - { id: 7, header: t("orderType", "Order Type"), key: "orderType" }, + { id: 4, header: t("test", "Test"), key: "test" }, + { id: 5, header: t("action", "Action"), key: "action" }, + { id: 6, header: t("status", "Status"), key: "status" }, + { id: 7, header: t("orderer", "Orderer"), key: "orderer" }, { id: 8, header: t("urgency", "Urgency"), key: "urgency" }, ]; const tableRows = useMemo(() => { - return paginatedWorkListEntries?.map((entry, index) => ({ - ...entry, - id: entry.uuid, - date: { - content: ( - <> - {formatDate(parseDate(entry.dateActivated))} - - ), - }, - 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} }, - })); + return paginatedWorkListEntries?.map((entry, index) => ( + + )); }, [paginatedWorkListEntries]); if (isLoading) { return ; } - if (paginatedWorkListEntries?.length >= 0) { + if (paginatedWorkListEntries?.length > 0) { return (
- - {({ - rows, - headers, - getHeaderProps, - getTableProps, - getRowProps, - onInputChange, - }) => ( - - - - - - { - setActivatedOnOrAfterDate(event.target.value); - }} - type="date" - value={activatedOnOrAfterDate} - /> - - - - - - - -
- - - - {headers.map((header) => ( - - {header.header?.content ?? 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); - } - }} - /> -
- )} -
+ +
+ ); + } else { + return ( +
+ +
+

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

+
+
); } diff --git a/src/completed-list/completed-list.scss b/src/completed-list/completed-list.scss index 0be385f..fa3c939 100644 --- a/src/completed-list/completed-list.scss +++ b/src/completed-list/completed-list.scss @@ -229,4 +229,5 @@ title { @include type.type-style('heading-compact-02'); color: $text-02; margin-bottom: 0.5rem; -} \ No newline at end of file +} + diff --git a/src/constants.ts b/src/constants.ts index 21ee9e3..e96e6ed 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,4 +1,4 @@ -export const moduleName = "@ugandaemr/openmrs-esm-laboratory-app"; +export const moduleName = "@openmrs/esm-laboratory-app"; export const LABORATARORY_ENCOUNTER_TYPE = "cbf01392-ca29-11e9-a32f-2a2ae2dbcce4"; diff --git a/src/index.ts b/src/index.ts index 4e0bcce..e3b431a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,9 +5,10 @@ import { } from "@openmrs/esm-framework"; import { configSchema } from "./config-schema"; import { createHomeDashboardLink } from "./components/create-dashboard-link.component"; + import { createDashboardLink } from "@openmrs/esm-patient-common-lib"; -const moduleName = "@ugandaemr/esm-laboratory-app"; +const moduleName = "@openmrs/esm-laboratory-app"; const options = { featureName: "ugandaemr-laboratory", @@ -107,6 +108,17 @@ export const worklistComponent = getAsyncLifecycle( options ); +export const pickLabRequestButton = getAsyncLifecycle( + () => import("./queue-list/pick-lab-request-menu.component"), + options +); + +export const rejectOrderButton = getAsyncLifecycle( + () => import("./order-actions/reject-order.component"), + + options +); + export function startupApp() { defineConfigSchema(moduleName, configSchema); } diff --git a/src/order-actions/reject-order.component.tsx b/src/order-actions/reject-order.component.tsx new file mode 100644 index 0000000..7d0b928 --- /dev/null +++ b/src/order-actions/reject-order.component.tsx @@ -0,0 +1,35 @@ +import React, { useCallback } from "react"; +import { OverflowMenuItem, OverflowMenu } from "@carbon/react"; +import { useTranslation } from "react-i18next"; +import { showModal } from "@openmrs/esm-framework"; +import { Result } from "../work-list/work-list.resource"; + +interface RejectOrderOverflowMenuItemProps { + order: Result; +} + +const RejectOrderOverflowMenuItem: React.FC< + RejectOrderOverflowMenuItemProps +> = ({ order }) => { + const { t } = useTranslation(); + + const handleRejectOrderModel = useCallback(() => { + const dispose = showModal("reject-order-dialog", { + closeModal: () => dispose(), + order, + }); + }, [order]); + return ( + + ); +}; + +export default RejectOrderOverflowMenuItem; 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 8062e5e..ed1b219 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 @@ -236,7 +236,7 @@ const AddToWorklistDialog: React.FC = ({
+ + + {concept?.answers?.map((answer) => ( + + {answer.display} + + ))} + + )} + /> + )} + + {isPanel(concept) && + concept.setMembers.map((member, index) => { + if (isTextOrNumeric(member)) { + return ( + ( + + )} + /> + ); + } + + if (isCoded(member)) { + return ( + ( + + )} + /> + ); + } + })} + + ); +}; + +export default ResultFormField; diff --git a/src/results/result-form.component.tsx b/src/results/result-form.component.tsx index 1540049..1a0a506 100644 --- a/src/results/result-form.component.tsx +++ b/src/results/result-form.component.tsx @@ -1,15 +1,6 @@ -import React, { useMemo, useState } from "react"; +import React, { useMemo } from "react"; import styles from "./result-form.scss"; -import { - Button, - InlineLoading, - TextInput, - Select, - SelectItem, - ModalBody, - ModalFooter, - ModalHeader, -} from "@carbon/react"; +import { Button, InlineLoading, ModalBody, ModalFooter } from "@carbon/react"; import { useTranslation } from "react-i18next"; import { closeOverlay } from "../components/overlay/hook"; import { @@ -19,10 +10,12 @@ import { usePatient, } from "@openmrs/esm-framework"; import { - UpdateEncounter, useGetOrderConceptByUuid, + UpdateOrderResult, } from "./result-form.resource"; import { Result } from "../work-list/work-list.resource"; +import ResultFormField from "./result-form-field.component"; +import { useForm } from "react-hook-form"; interface ResultFormProps { patientUuid: string; @@ -31,16 +24,19 @@ interface ResultFormProps { const ResultForm: React.FC = ({ order, patientUuid }) => { const { t } = useTranslation(); + const { + control, + register, + formState: { isSubmitting }, + getValues, + } = useForm<{ testResult: string }>({ + defaultValues: {}, + }); 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 { concept, isLoading: isLoadingConcepts } = useGetOrderConceptByUuid( + order.concept.uuid + ); const bannerState = useMemo(() => { if (patient) { @@ -52,127 +48,102 @@ const ResultForm: React.FC = ({ order, patientUuid }) => { } }, [patient, patientUuid]); - // getInput values - const handleInputChange = (memberUuid, value) => { - setInputValues((prevValues) => ({ - ...prevValues, - [memberUuid]: value, - })); - }; + if (isLoadingConcepts) { + return
Loading test details
; + } - // create input fields - const Questions = ({ conceptMembers }) => { - const inputFields = useMemo(() => { - return conceptMembers.map((member) => { - let inputField; + const handleSubmit = (e) => { + e.preventDefault(); + // assign result to test order + const documentedValues = getValues(); + let obsValue = []; + if (concept.set && concept.setMembers.length > 0) { + let groupMembers = []; + concept.setMembers.forEach((member) => { + let value; if ( - member.datatype.display === "Text" || - member.datatype.display === "Numeric" + member.datatype.display === "Numeric" || + member.datatype.display === "Text" ) { - inputField = ( - handleInputChange(member.uuid, e.target.value)} - /> - ); + value = getValues()[`${member.uuid}`]; } else if (member.datatype.display === "Coded") { - inputField = ( - - ); + value = { + uuid: getValues()[`${member.uuid}`], + }; } - - return inputField; + const groupMember = { + concept: { uuid: member.uuid }, + value: value, + status: "FINAL", + order: { uuid: order.uuid }, + }; + groupMembers.push(groupMember); }); - }, [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) => { + obsValue.push({ + concept: { uuid: order.concept.uuid }, + status: "FINAL", + order: { uuid: order.uuid }, + groupMembers: groupMembers, + }); + } else if (!concept.set && concept.setMembers.length === 0) { let value; if ( - item.datatype.display === "Numeric" || - item.datatype.display === "Text" + concept.datatype.display === "Numeric" || + concept.datatype.display === "Text" ) { - value = inputValues[`${item.uuid}`]; - } else if (item.datatype.display === "Coded") { + value = getValues()[`${concept.uuid}`]; + } else if (concept.datatype.display === "Coded") { value = { - uuid: inputValues[`${item.uuid}`], + uuid: getValues()[`${concept.uuid}`], }; } - const groupMember = { - concept: { uuid: item.uuid }, - value: value, + + obsValue.push({ + concept: { uuid: order.concept.uuid }, status: "FINAL", order: { uuid: order.uuid }, - }; - groupMembers.push(groupMember); - }); - obsValue.push(ob); + value: value, + }); + } - const payload = { + const obsPayload = { obs: obsValue, }; - setIsSubmitting(true); - // update encounter - UpdateEncounter(order.encounter.uuid, payload).then( + + const orderDiscontinuationPayload = { + previousOrder: order.uuid, + type: "testorder", + action: "DISCONTINUE", + careSetting: order.careSetting.uuid, + encounter: order.encounter.uuid, + patient: order.patient.uuid, + concept: order.concept.uuid, + orderer: order.orderer, + }; + + UpdateOrderResult( + order.encounter.uuid, + obsPayload, + orderDiscontinuationPayload + ).then( () => { - setIsSubmitting(false); showToast({ critical: true, - title: t("updateEncounter", "Update Encounter"), + title: t("updateEncounter", "Update lab results"), kind: "success", description: t( "generateSuccessfully", - "You have successfully encounter with test results" + "You have successfully updated test results" ), }); + closeOverlay(); }, (err) => { - setIsSubmitting(false); showNotification({ title: t( - `errorUpdatingEncounter', 'Error occurred while updating encounter` + `errorUpdatingEncounter', 'Error occurred while updating test results` ), kind: "error", critical: true, @@ -181,7 +152,6 @@ const ResultForm: React.FC = ({ order, patientUuid }) => { } ); }; - return ( <>
@@ -197,10 +167,17 @@ const ResultForm: React.FC = ({ order, patientUuid }) => { {patient && ( )} - - {concept?.length > 0 && ( + {/* // we need to display test name for test panels */} + {concept.setMembers.length > 0 && ( +
Test panel: {concept.display}
+ )} + {concept && (
- +
)} @@ -213,7 +190,7 @@ const ResultForm: React.FC = ({ order, patientUuid }) => { > {t("cancel", "Cancel")} - +
diff --git a/src/results/result-form.resource.ts b/src/results/result-form.resource.ts index dac553e..0f9c957 100644 --- a/src/results/result-form.resource.ts +++ b/src/results/result-form.resource.ts @@ -14,7 +14,7 @@ export interface ConceptResponse { descriptions: Description[]; mappings: Mapping[]; answers: any[]; - setMembers: SetMember[]; + setMembers: ConceptReference[]; auditInfo: AuditInfo; attributes: any[]; links: Link18[]; @@ -142,7 +142,7 @@ export interface Link8 { resourceAlias: string; } -export interface SetMember { +export interface ConceptReference { uuid: string; display: string; name: Name3; @@ -306,7 +306,7 @@ export function useGetOrderConceptByUuid(uuid: string) { Error >(apiUrl, openmrsFetch); return { - concept: data?.data ? data?.data.setMembers : [], + concept: data?.data, isLoading, isError: error, isValidating, @@ -326,3 +326,33 @@ export async function UpdateEncounter(uuid: string, payload: any) { body: payload, }); } + +//TODO: the calls to update order and observations for results should be transactional to allow for rollback +export async function UpdateOrderResult( + encounterUuid: string, + obsPayload: any, + orderPayload: any +) { + const abortController = new AbortController(); + const updateOrderCall = await openmrsFetch(`/ws/rest/v1/order`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + signal: abortController.signal, + body: orderPayload, + }); + + if (updateOrderCall.status === 201) { + return await openmrsFetch(`/ws/rest/v1/encounter/${encounterUuid}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + signal: abortController.signal, + body: obsPayload, + }); + } else { + // handle errors + } +} diff --git a/src/review-list/review-list.component.tsx b/src/review-list/review-list.component.tsx index 2eaedb8..f91da5a 100644 --- a/src/review-list/review-list.component.tsx +++ b/src/review-list/review-list.component.tsx @@ -94,17 +94,18 @@ const ReviewList: React.FC = ({ fulfillerStatus }) => { { id: 0, header: t("date", "Date"), key: "date" }, { id: 1, header: t("orderNumber", "Order Number"), key: "orderNumber" }, + { id: 2, header: t("patient", "Patient"), key: "patient" }, + { - id: 2, + id: 3, header: t("accessionNumber", "Accession Number"), key: "accessionNumber", }, - { id: 3, header: t("test", "Test"), key: "test" }, - { id: 4, header: t("action", "Action"), key: "action" }, - { id: 5, header: t("status", "Status"), key: "status" }, - { id: 6, header: t("orderer", "Orderer"), key: "orderer" }, - { id: 7, header: t("orderType", "Order Type"), key: "orderType" }, - { id: 8, header: t("urgency", "Urgency"), key: "urgency" }, + { id: 4, header: t("test", "Test"), key: "test" }, + { id: 5, header: t("action", "Action"), key: "action" }, + { id: 6, header: t("status", "Status"), key: "status" }, + { id: 8, header: t("orderer", "Orderer"), key: "orderer" }, + { id: 9, header: t("urgency", "Urgency"), key: "urgency" }, ]; const tableRows = useMemo(() => { @@ -118,6 +119,13 @@ const ReviewList: React.FC = ({ fulfillerStatus }) => { ), }, + patient: { + content: ( + <> + {entry.patient.display.split("-")[1]} + + ), + }, orderNumber: { content: {entry.orderNumber} }, accessionNumber: { content: {entry.accessionNumber} }, test: { content: {entry.concept.display} }, diff --git a/src/routes.json b/src/routes.json index fd09735..2a27b67 100644 --- a/src/routes.json +++ b/src/routes.json @@ -103,6 +103,14 @@ "name": "approvedPanelSlot", "title": "Approved" } + "name": "pick-lab-request-button", + "component": "pickLabRequestButton", + "slot": "order-actions-slot" + }, + { + "name": "reject-order-button", + "component": "rejectOrderButton", + "slot": "order-actions-slot" } ] diff --git a/src/summary-tiles/summary-tile.component.tsx b/src/summary-tiles/summary-tile.component.tsx index 545a548..bae9078 100644 --- a/src/summary-tiles/summary-tile.component.tsx +++ b/src/summary-tiles/summary-tile.component.tsx @@ -35,6 +35,7 @@ const SummaryTile: React.FC = ({ iconDescription={t("view", "View ")} > {t("view", "View ")} +  
diff --git a/src/ui-components/overflow-menu.component.tsx b/src/ui-components/overflow-menu.component.tsx new file mode 100644 index 0000000..906324f --- /dev/null +++ b/src/ui-components/overflow-menu.component.tsx @@ -0,0 +1,88 @@ +import React, { useState, useCallback, useEffect, useRef } from "react"; +import classNames from "classnames"; +import { useLayoutType } from "@openmrs/esm-framework"; +import styles from "./overflow-menu.scss"; + +interface OrderCustomOverflowMenuComponentProps { + menuTitle: React.ReactNode; + children: React.ReactNode; +} + +const OrderCustomOverflowMenuComponent: React.FC< + OrderCustomOverflowMenuComponentProps +> = ({ children, menuTitle }) => { + const [showMenu, setShowMenu] = useState(false); + const isTablet = useLayoutType() === "tablet"; + const wrapperRef = useRef(null); + + const toggleShowMenu = useCallback(() => setShowMenu((state) => !state), []); + + useEffect(() => { + /** + * Toggle showMenu if clicked on outside of element + */ + function handleClickOutside(event: MouseEvent) { + if (wrapperRef.current && !wrapperRef.current.contains(event.target)) { + setShowMenu(false); + } + } + + // Bind the event listener + document.addEventListener("mousedown", handleClickOutside); + return () => { + // Unbind the event listener on clean up + document.removeEventListener("mousedown", handleClickOutside); + }; + }, [wrapperRef]); + + return ( +
+ + +
+ ); +}; + +export default OrderCustomOverflowMenuComponent; diff --git a/src/ui-components/overflow-menu.scss b/src/ui-components/overflow-menu.scss new file mode 100644 index 0000000..c0d70b9 --- /dev/null +++ b/src/ui-components/overflow-menu.scss @@ -0,0 +1,39 @@ +@use '@carbon/colors'; +@import '~@openmrs/esm-styleguide/src/vars'; + +.container { + position: relative; + top: 0px; +} + +.menu { + display: none; + top: 3.125rem; + min-width: initial; + left: auto; + right: 0; + background-color: $ui-01; + margin-right: 0.2rem; + box-shadow: 0 6px 6px rgb(0 0 0 / 30%); +} + +.show { + display: block; +} + +.overflowMenuButton { + width: auto; + height: auto; + padding: 0.875rem 1rem; + color: colors.$blue-60; + display: flex; + align-items: center; + + &:hover { + background-color: colors.$gray-10-hover; + } +} + +.deceased { + color: colors.$blue-40; +} \ No newline at end of file diff --git a/src/work-list/work-list.component.tsx b/src/work-list/work-list.component.tsx index 117894f..1f7cf1e 100644 --- a/src/work-list/work-list.component.tsx +++ b/src/work-list/work-list.component.tsx @@ -101,18 +101,19 @@ const WorkList: React.FC = ({ fulfillerStatus }) => { { id: 0, header: t("date", "Date"), key: "date" }, { id: 1, header: t("orderNumber", "Order Number"), key: "orderNumber" }, + { id: 2, header: t("patient", "Patient"), key: "patient" }, + { - id: 2, + id: 3, header: t("accessionNumber", "Accession Number"), key: "accessionNumber", }, - { id: 3, header: t("test", "Test"), key: "test" }, - { id: 4, header: t("action", "Action"), key: "action" }, - { id: 5, header: t("status", "Status"), key: "status" }, - { id: 6, header: t("orderer", "Orderer"), key: "orderer" }, - { id: 7, header: t("orderType", "Order Type"), key: "orderType" }, - { id: 8, header: t("urgency", "Urgency"), key: "urgency" }, - { id: 9, header: t("actions", "Actions"), key: "actions" }, + { id: 4, header: t("test", "Test"), key: "test" }, + { id: 5, header: t("action", "Action"), key: "action" }, + { id: 6, header: t("status", "Status"), key: "status" }, + { id: 8, header: t("orderer", "Orderer"), key: "orderer" }, + { id: 9, header: t("urgency", "Urgency"), key: "urgency" }, + { id: 10, header: t("actions", "Actions"), key: "actions" }, ]; const ResultsOrder: React.FC = ({ @@ -124,7 +125,7 @@ const WorkList: React.FC = ({ fulfillerStatus }) => { kind="ghost" onClick={() => { launchOverlay( - t("resultForm", "Result Tests"), + t("resultForm", "Lab results form"), ); }} @@ -135,7 +136,7 @@ const WorkList: React.FC = ({ fulfillerStatus }) => { const tableRows = useMemo(() => { return paginatedWorkListEntries - ?.filter((item) => item.action === "REVISE") + ?.filter((item) => item.fulfillerStatus === "IN_PROGRESS") .map((entry, index) => ({ ...entry, id: entry.uuid, @@ -146,6 +147,13 @@ const WorkList: React.FC = ({ fulfillerStatus }) => { ), }, + patient: { + content: ( + <> + {entry.patient.display.split("-")[1]} + + ), + }, orderNumber: { content: {entry.orderNumber} }, accessionNumber: { content: {entry.accessionNumber} }, test: { content: {entry.concept.display} }, @@ -267,7 +275,7 @@ const WorkList: React.FC = ({ fulfillerStatus }) => {

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

diff --git a/translations/en.json b/translations/en.json index ab24227..d624627 100644 --- a/translations/en.json +++ b/translations/en.json @@ -12,5 +12,7 @@ "transferred": "Transferred", "visitId": "Visit ID", "waitingTime": "Waiting time", - "worklist": "Worklist" + "worklist": "Worklist", + "rejectOrder": "Reject Order", + "pickLabRequest": "Pick Lab Request" } diff --git a/yarn.lock b/yarn.lock index 0759deb..0a768cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1684,6 +1684,15 @@ __metadata: languageName: node linkType: hard +"@hookform/resolvers@npm:^3.3.4": + version: 3.3.4 + resolution: "@hookform/resolvers@npm:3.3.4" + peerDependencies: + react-hook-form: ^7.0.0 + checksum: 7761e3340d23acd092dec4023344678e62b0c0ea0ed5aa3687cd315fc339b3b965e11124d643811292e6962683357423e6fe646fffabf9b96f8322f41903924d + languageName: node + linkType: hard + "@humanwhocodes/config-array@npm:^0.11.13": version: 0.11.13 resolution: "@humanwhocodes/config-array@npm:0.11.13" @@ -4174,6 +4183,80 @@ __metadata: languageName: node linkType: hard +"@openmrs/esm-laboratory-app@workspace:.": + version: 0.0.0-use.local + resolution: "@openmrs/esm-laboratory-app@workspace:." + dependencies: + "@carbon/react": ^1.14.0 + "@hookform/resolvers": ^3.3.4 + "@ohri/openmrs-esm-ohri-commons-lib": next + "@openmrs/esm-extensions": next + "@openmrs/esm-framework": next + "@openmrs/esm-patient-common-lib": next + "@openmrs/esm-react-utils": next + "@openmrs/esm-styleguide": next + "@swc/cli": ^0.1.62 + "@swc/core": ^1.3.62 + "@swc/jest": ^0.2.26 + "@testing-library/dom": ^8.20.0 + "@testing-library/jest-dom": ^5.16.5 + "@testing-library/react": ^13.4.0 + "@testing-library/user-event": ^14.4.3 + "@types/jest": ^28.1.8 + "@types/react": ^18.2.8 + "@types/react-dom": ^18.2.4 + "@types/react-router": ^5.1.20 + "@types/react-router-dom": ^5.3.3 + "@types/webpack-env": ^1.18.1 + "@typescript-eslint/parser": ^5.59.9 + carbon-components-react: ^8.34.0 + concurrently: ^7.6.0 + css-loader: ^6.8.1 + dayjs: ^1.11.9 + eslint: ^8.42.0 + eslint-config-prettier: ^8.8.0 + eslint-config-ts-react-important-stuff: ^3.0.0 + eslint-plugin-prettier: ^4.2.1 + file-saver: ^2.0.5 + husky: ^8.0.0 + identity-obj-proxy: ^3.0.0 + jest: ^28.1.3 + jest-cli: ^28.1.3 + jest-environment-jsdom: ^28.1.3 + lerna: ^5.6.1 + lodash-es: ^4.17.21 + openmrs: next + plotly.js: ^2.24.3 + prettier: ^2.8.8 + pretty-quick: ^3.1.3 + raw-loader: ^4.0.2 + react: ^18.2.0 + react-csv: ^2.2.2 + react-dom: ^18.2.0 + react-hook-form: ^7.49.3 + react-i18next: ^11.18.6 + react-pivottable: ^0.11.0 + react-plotly.js: ^2.0.0 + react-router-dom: ^6.11.2 + react-table: ^7.8.0 + react-to-print: ^2.14.15 + rxjs: ^6.6.7 + swc-loader: ^0.2.3 + turbo: ^1.10.12 + typescript: ^4.9.5 + webpack: ^5.88.1 + webpack-cli: ^5.1.3 + zod: ^3.22.4 + peerDependencies: + "@openmrs/esm-framework": "*" + dayjs: 1.x + react: 18.x + react-i18next: 11.x + react-router-dom: 6.x + rxjs: 6.x + languageName: unknown + linkType: soft + "@openmrs/esm-offline@npm:^5.2.1-pre.1120": version: 5.2.1-pre.1129 resolution: "@openmrs/esm-offline@npm:5.2.1-pre.1129" @@ -6535,77 +6618,6 @@ __metadata: languageName: node linkType: hard -"@ugandaemr/openmrs-esm-laboratory-app@workspace:.": - version: 0.0.0-use.local - resolution: "@ugandaemr/openmrs-esm-laboratory-app@workspace:." - dependencies: - "@carbon/react": ^1.14.0 - "@ohri/openmrs-esm-ohri-commons-lib": next - "@openmrs/esm-extensions": next - "@openmrs/esm-framework": next - "@openmrs/esm-patient-common-lib": next - "@openmrs/esm-react-utils": next - "@openmrs/esm-styleguide": next - "@swc/cli": ^0.1.62 - "@swc/core": ^1.3.62 - "@swc/jest": ^0.2.26 - "@testing-library/dom": ^8.20.0 - "@testing-library/jest-dom": ^5.16.5 - "@testing-library/react": ^13.4.0 - "@testing-library/user-event": ^14.4.3 - "@types/jest": ^28.1.8 - "@types/react": ^18.2.8 - "@types/react-dom": ^18.2.4 - "@types/react-router": ^5.1.20 - "@types/react-router-dom": ^5.3.3 - "@types/webpack-env": ^1.18.1 - "@typescript-eslint/parser": ^5.59.9 - carbon-components-react: ^8.34.0 - concurrently: ^7.6.0 - css-loader: ^6.8.1 - dayjs: ^1.11.9 - eslint: ^8.42.0 - eslint-config-prettier: ^8.8.0 - eslint-config-ts-react-important-stuff: ^3.0.0 - eslint-plugin-prettier: ^4.2.1 - file-saver: ^2.0.5 - husky: ^8.0.0 - identity-obj-proxy: ^3.0.0 - jest: ^28.1.3 - jest-cli: ^28.1.3 - jest-environment-jsdom: ^28.1.3 - lerna: ^5.6.1 - lodash-es: ^4.17.21 - openmrs: next - plotly.js: ^2.24.3 - prettier: ^2.8.8 - pretty-quick: ^3.1.3 - raw-loader: ^4.0.2 - react: ^18.2.0 - react-csv: ^2.2.2 - react-dom: ^18.2.0 - react-i18next: ^11.18.6 - react-pivottable: ^0.11.0 - react-plotly.js: ^2.0.0 - react-router-dom: ^6.11.2 - react-table: ^7.8.0 - react-to-print: ^2.14.15 - rxjs: ^6.6.7 - swc-loader: ^0.2.3 - turbo: ^1.10.12 - typescript: ^4.9.5 - webpack: ^5.88.1 - webpack-cli: ^5.1.3 - peerDependencies: - "@openmrs/esm-framework": "*" - dayjs: 1.x - react: 18.x - react-i18next: 11.x - react-router-dom: 6.x - rxjs: 6.x - languageName: unknown - linkType: soft - "@ungap/structured-clone@npm:^1.2.0": version: 1.2.0 resolution: "@ungap/structured-clone@npm:1.2.0" @@ -19112,6 +19124,15 @@ __metadata: languageName: node linkType: hard +"react-hook-form@npm:^7.49.3": + version: 7.49.3 + resolution: "react-hook-form@npm:7.49.3" + peerDependencies: + react: ^16.8.0 || ^17 || ^18 + checksum: 9769845e2749f5c9d1d033be3819bc572ffa67f19ec2e567c61a58d0d060107facdf8425aab253c2e667c00853b7706a50bd6739fca6228c284c9313420ca26f + languageName: node + linkType: hard + "react-i18next@npm:^11.18.6": version: 11.18.6 resolution: "react-i18next@npm:11.18.6" @@ -23615,6 +23636,13 @@ __metadata: languageName: node linkType: hard +"zod@npm:^3.22.4": + version: 3.22.4 + resolution: "zod@npm:3.22.4" + checksum: 80bfd7f8039b24fddeb0718a2ec7c02aa9856e4838d6aa4864335a047b6b37a3273b191ef335bf0b2002e5c514ef261ffcda5a589fb084a48c336ffc4cdbab7f + languageName: node + linkType: hard + "zustand@npm:^4.3.6": version: 4.4.6 resolution: "zustand@npm:4.4.6"