diff --git a/package.json b/package.json index 0121fe47..6132a292 100644 --- a/package.json +++ b/package.json @@ -1,123 +1,123 @@ { - "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", - "main": "src/index.ts", - "source": true, - "scripts": { - "start": "openmrs develop", - "serve": "webpack serve --mode=development", - "build": "webpack --mode production", - "analyze": "webpack --mode=production --env analyze=true", - "lint": "TIMING=1 eslint src --ext js,jsx,ts,tsx", - "prettier": "prettier --write \"src/**/*.{ts,tsx}\"", - "typescript": "tsc", - "test": "jest --config jest.config.js --passWithNoTests", - "verify": "turbo lint typescript test", - "extract-translations": "i18next 'src/**/*.component.tsx' --config ./i18next-parser.config.js", - "coverage": "yarn test -- --coverage", - "prepare": "husky install" - }, - "husky": { - "hooks": { - "pre-commit": "pretty-quick --staged && yarn verify" - } - }, - "browserslist": [ - "extends browserslist-config-openmrs" - ], - "keywords": [ - "openmrs", - "microfrontends", - "laboratory" - ], - "repository": { - "type": "git", - "url": "git+https://github.com/openmrs/openmrs-esm-laboratory.git" - }, - "homepage": "https://github.com/openmrs/openmrs-esm-laboratory#readme", - "publishConfig": { - "access": "public" - }, - "bugs": { - "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-hook-form": "^7.49.3", - "react-to-print": "^2.14.15", - "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" - }, - "devDependencies": { - "@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", - "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", - "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" - }, - "resolutions": { - "@carbon/react": "1.14.0" - }, - "packageManager": "yarn@3.6.3" + "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", + "main": "src/index.ts", + "source": true, + "scripts": { + "start": "openmrs develop", + "serve": "webpack serve --mode=development", + "build": "webpack --mode production", + "analyze": "webpack --mode=production --env analyze=true", + "lint": "TIMING=1 eslint src --ext js,jsx,ts,tsx", + "prettier": "prettier --write \"src/**/*.{ts,tsx}\"", + "typescript": "tsc", + "test": "jest --config jest.config.js --passWithNoTests", + "verify": "turbo lint typescript test", + "extract-translations": "i18next 'src/**/*.component.tsx' --config ./i18next-parser.config.js", + "coverage": "yarn test -- --coverage", + "prepare": "husky install" + }, + "husky": { + "hooks": { + "pre-commit": "pretty-quick --staged && yarn verify" + } + }, + "browserslist": [ + "extends browserslist-config-openmrs" + ], + "keywords": [ + "openmrs", + "microfrontends", + "laboratory" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/openmrs/openmrs-esm-laboratory.git" + }, + "homepage": "https://github.com/openmrs/openmrs-esm-laboratory#readme", + "publishConfig": { + "access": "public" + }, + "bugs": { + "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-hook-form": "^7.49.3", + "react-to-print": "^2.14.15", + "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" + }, + "devDependencies": { + "@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", + "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", + "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" + }, + "resolutions": { + "@carbon/react": "1.14.0" + }, + "packageManager": "yarn@3.6.3" } diff --git a/src/config-schema.ts b/src/config-schema.ts index e008d16f..4d7608d0 100644 --- a/src/config-schema.ts +++ b/src/config-schema.ts @@ -31,6 +31,12 @@ export const configSchema = { _default: "b1f8b6c8-c255-4518-89f5-4236ab76025b", _description: "Concept uuid for laboratory referals destinations", }, + + enableSendingLabTestsByEmail: { + _type: Type.Boolean, + _default: false, + _description: "This enables sending results to patient via email", + }, }; export type Config = { diff --git a/src/index.ts b/src/index.ts index 12313d9c..ec47518d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,12 @@ import { import { configSchema } from "./config-schema"; import { createHomeDashboardLink } from "./components/create-dashboard-link.component"; -import { createDashboardLink } from "@openmrs/esm-patient-common-lib"; +import laboratoryReferralWorkspaceComponent from "./patient-chart/laboratory-workspaces/laboratory-referral.workspace.component"; + +import { + createDashboardLink, + registerWorkspace, +} from "@openmrs/esm-patient-common-lib"; const moduleName = "@openmrs/esm-laboratory-app"; @@ -51,7 +56,7 @@ export const laboratoryOrderDashboardLink = getSyncLifecycle( options ); export const laboratoryOrderComponent = getAsyncLifecycle( - () => import("./patient-chart/laboratory-order.component"), + () => import("./patient-chart/patient-laboratory-order-results.component"), options ); @@ -140,4 +145,9 @@ export const testOrderedTileComponent = getAsyncLifecycle( export function startupApp() { defineConfigSchema(moduleName, configSchema); + registerWorkspace({ + name: "patient-laboratory-referral-workspace", + title: "Laboratory Referral Form", + load: getSyncLifecycle(laboratoryReferralWorkspaceComponent, options), + }); } diff --git a/src/patient-chart/laboratory-active-test-order/laboratory-active-test-order-results.component.tsx b/src/patient-chart/laboratory-active-test-order/laboratory-active-test-order-results.component.tsx new file mode 100644 index 00000000..d4123758 --- /dev/null +++ b/src/patient-chart/laboratory-active-test-order/laboratory-active-test-order-results.component.tsx @@ -0,0 +1,473 @@ +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import { useTranslation } from "react-i18next"; +import styles from "./laboratory-active-test-order-results.scss"; +import { + formatDate, + parseDate, + ErrorState, + showModal, + useConfig, +} from "@openmrs/esm-framework"; +import { mutate } from "swr"; +import { + DataTable, + DataTableSkeleton, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableHeader, + TableRow, + TableToolbar, + TableToolbarContent, + TableToolbarSearch, + Layer, + Tag, + Tile, + Tooltip, + Pagination, + TableExpandHeader, + TableExpandRow, + TableExpandedRow, + Button, + IconButton, + InlineLoading, +} from "@carbon/react"; + +import { + Printer, + MailAll, + Add, + Checkmark, + SendAlt, + NotSent, +} from "@carbon/react/icons"; + +import TestsResults from "../results-summary/test-results-table.component"; +import { useReactToPrint } from "react-to-print"; +import PrintResultsSummary from "../results-summary/print-results-summary.component"; +import { EncounterResponse } from "../laboratory-item/view-laboratory-item.resource"; +import { useGetPatientByUuid } from "../../utils/functions"; +import { + ResourceRepresentation, + getOrderColor, +} from "../patient-laboratory-order-results.resource"; +import { useLaboratoryOrderResultsPages } from "../patient-laboratory-order-results-table.resource"; +import { + CardHeader, + launchPatientWorkspace, +} from "@openmrs/esm-patient-common-lib"; + +interface LaboratoryActiveTestOrderResultsProps { + patientUuid: string; +} + +interface PrintProps { + encounter: EncounterResponse; +} + +const LaboratoryActiveTestOrderResults: React.FC< + LaboratoryActiveTestOrderResultsProps +> = ({ patientUuid }) => { + const { t } = useTranslation(); + + const { enableSendingLabTestsByEmail } = useConfig(); + + const displayText = t( + "activelLaboratoryTestsDisplayTextTitle", + "Active Laboratory Tests" + ); + + const { + items, + tableHeaders, + currentPage, + pageSizes, + totalItems, + goTo, + currentPageSize, + setPageSize, + isLoading, + isError, + } = useLaboratoryOrderResultsPages({ + v: ResourceRepresentation.Full, + totalCount: true, + patientUuid: patientUuid, + }); + + const sortedLabRequests = useMemo(() => { + return [...items].sort((a, b) => { + const dateA = new Date(a.encounterDatetime); + const dateB = new Date(b.encounterDatetime); + return dateB.getTime() - dateA.getTime(); + }); + }, [items]); + + const [searchTerm, setSearchTerm] = useState(""); + const [laboratoryOrders, setLaboratoryOrders] = useState(sortedLabRequests); + const [initialTests, setInitialTests] = useState(sortedLabRequests); + + const handleChange = useCallback((event) => { + const searchText = event?.target?.value?.trim().toLowerCase(); + setSearchTerm(searchText); + }, []); + + useEffect(() => { + if (!searchTerm) { + setLaboratoryOrders(initialTests); + } else { + const filteredItems = initialTests.filter((item) => + item?.orders?.some((order) => + order?.concept?.display.toLowerCase().includes(searchTerm) + ) + ); + setLaboratoryOrders(filteredItems); + } + }, [searchTerm, initialTests]); + + useEffect(() => { + setInitialTests(sortedLabRequests); + }, [sortedLabRequests]); + + const EmailButtonAction: React.FC = () => { + const launchSendEmailModal = useCallback(() => { + const dispose = showModal("send-email-dialog", { + closeModal: () => dispose(), + }); + }, []); + + return ( + + + + + + {({ rows, headers, getHeaderProps, getTableProps, getRowProps }) => ( + + + +
+ Key: + } + > + {"Requested"} + + } + > + {"Completed"} + + } + > + {"Rejected"} + +
+ + + +
+
+ + + + + {headers.map((header) => ( + + {header.header} + + ))} + + + + {rows.map((row, index) => { + return ( + + + {row.cells.map((cell) => ( + + {cell.value?.content ?? cell.value} + + ))} + + {row.isExpanded ? ( + + + + ) : ( + + )} + + ); + })} + +
+ {rows.length === 0 ? ( +
+ +
+

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

+

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

+

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

+ +
+
+
+ ) : null} + { + if (pageSize !== currentPageSize) { + setPageSize(pageSize); + } + if (page !== currentPage) { + goTo(page); + } + }} + className={styles.paginationOverride} + /> +
+ )} +
+ + ); + } +}; + +export default LaboratoryActiveTestOrderResults; diff --git a/src/patient-chart/laboratory-order.scss b/src/patient-chart/laboratory-active-test-order/laboratory-active-test-order-results.scss similarity index 76% rename from src/patient-chart/laboratory-order.scss rename to src/patient-chart/laboratory-active-test-order/laboratory-active-test-order-results.scss index 033bcc3d..1827a1a6 100644 --- a/src/patient-chart/laboratory-order.scss +++ b/src/patient-chart/laboratory-active-test-order/laboratory-active-test-order-results.scss @@ -1,7 +1,20 @@ @use '@carbon/styles/scss/spacing'; +@use '@carbon/styles/scss/colors'; @use '@carbon/styles/scss/type'; @import "~@openmrs/esm-styleguide/src/vars"; -@import '../root.scss'; +@import '../../root.scss'; + + +.widgetCard { + border: 1px solid colors.$gray-20; + border-bottom: none; +} + +.headerBtnContainer { + background-color: $ui-background; + padding: spacing.$spacing-05; + text-align: right; +} .tileContainer { background-color: $ui-02; @@ -19,6 +32,7 @@ flex-direction: column; align-items: center; } + .content { @include type.type-style('heading-compact-02'); color: $text-02; @@ -30,6 +44,10 @@ color: $text-02; } +.container { + background-color: $ui-01; +} + .separator { @include type.type-style('body-compact-02'); color: $text-02; @@ -58,4 +76,4 @@ left: 0.5rem; margin-right: -50%; } -} +} \ No newline at end of file diff --git a/src/patient-chart/laboratory-order-referals/laboratory-order-referals.component.tsx b/src/patient-chart/laboratory-order-referals/laboratory-order-referals.component.tsx new file mode 100644 index 00000000..4875023f --- /dev/null +++ b/src/patient-chart/laboratory-order-referals/laboratory-order-referals.component.tsx @@ -0,0 +1,506 @@ +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import { useTranslation } from "react-i18next"; +import styles from "./laboratory-order-referals.scss"; +import { + formatDate, + parseDate, + ErrorState, + showModal, + useConfig, +} from "@openmrs/esm-framework"; + +import { + DataTable, + DataTableSkeleton, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableHeader, + TableRow, + TableToolbar, + TableToolbarContent, + TableToolbarSearch, + Layer, + Tag, + Tile, + Pagination, + TableExpandHeader, + TableExpandRow, + TableExpandedRow, + Button, + IconButton, + InlineLoading, +} from "@carbon/react"; + +import { + Printer, + MailAll, + Add, + Checkmark, + SendAlt, + NotSent, + Edit, +} from "@carbon/react/icons"; +import TestsResults from "../results-summary/test-results-table.component"; +import { useReactToPrint } from "react-to-print"; +import PrintResultsSummary from "../results-summary/print-results-summary.component"; +import { EncounterResponse } from "../laboratory-item/view-laboratory-item.resource"; +import { useGetPatientByUuid } from "../../utils/functions"; +import { + ResourceRepresentation, + getOrderColor, +} from "../patient-laboratory-order-results.resource"; +import { useLaboratoryOrderResultsPages } from "../patient-laboratory-order-results-table.resource"; +import { + CardHeader, + launchPatientWorkspace, +} from "@openmrs/esm-patient-common-lib"; +import { mutate } from "swr"; + +interface LaboratoryOrderReferalResultsProps { + patientUuid: string; +} + +interface EditReferralActionProps { + formUuid: string; + encounterUuid: string; +} + +interface PrintProps { + encounter: EncounterResponse; +} + +const LaboratoryOrderReferalResults: React.FC< + LaboratoryOrderReferalResultsProps +> = ({ patientUuid }) => { + const { t } = useTranslation(); + + const { enableSendingLabTestsByEmail } = useConfig(); + + const displayText = t( + "referralLaboratoryTestsDisplayTextTitle", + "Laboratory Referral Tests" + ); + + const { + items, + tableHeaders, + currentPage, + pageSizes, + totalItems, + goTo, + currentPageSize, + setPageSize, + isLoading, + isError, + } = useLaboratoryOrderResultsPages({ + v: ResourceRepresentation.Full, + totalCount: true, + patientUuid: patientUuid, + }); + + const sortedLabRequests = useMemo(() => { + return [...items].sort((a, b) => { + const dateA = new Date(a.encounterDatetime); + const dateB = new Date(b.encounterDatetime); + return dateB.getTime() - dateA.getTime(); + }); + }, [items]); + + const [searchTerm, setSearchTerm] = useState(""); + const [laboratoryOrders, setLaboratoryOrders] = useState(sortedLabRequests); + const [initialTests, setInitialTests] = useState(sortedLabRequests); + + const handleChange = useCallback((event) => { + const searchText = event?.target?.value?.trim().toLowerCase(); + setSearchTerm(searchText); + }, []); + + useEffect(() => { + if (!searchTerm) { + setLaboratoryOrders(initialTests); + } else { + const filteredItems = initialTests.filter((item) => + item?.orders?.some((order) => + order?.concept?.display.toLowerCase().includes(searchTerm) + ) + ); + setLaboratoryOrders(filteredItems); + } + }, [searchTerm, initialTests]); + + useEffect(() => { + setInitialTests(sortedLabRequests); + }, [sortedLabRequests]); + + const EmailButtonAction: React.FC = () => { + const launchSendEmailModal = useCallback(() => { + const dispose = showModal("send-email-dialog", { + closeModal: () => dispose(), + }); + }, []); + + return ( +