From d4cd290494513732ba9c31f54fa4942be88e28e6 Mon Sep 17 00:00:00 2001 From: Jovan Ssebaggala Date: Thu, 29 Aug 2024 12:04:35 +0300 Subject: [PATCH 1/6] (chore) Bump react form engine version (#1978) Bump FE version --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index d95d1e6437..301eb0a3ef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5330,8 +5330,8 @@ __metadata: linkType: soft "@openmrs/esm-form-engine-lib@npm:next": - version: 2.1.0-pre.1382 - resolution: "@openmrs/esm-form-engine-lib@npm:2.1.0-pre.1382" + version: 2.1.0-pre.1384 + resolution: "@openmrs/esm-form-engine-lib@npm:2.1.0-pre.1384" dependencies: "@carbon/react": "npm:>1.47.0 <1.50.0" classnames: "npm:^2.5.1" @@ -5349,7 +5349,7 @@ __metadata: react: 18.x react-i18next: 11.x swr: 2.x - checksum: 10/ea696eb023a9a09d0ead5979ed1c5c0c73641175b1c95f83caa357192bd3757942c2f37e3bf9f0c5f2255de241103689e41271668ead82bfa7762701579e84a9 + checksum: 10/85d380e9081ae844865de5425f3f803d947fb28e875eb03cfd9ba8c976b8ead7b7c72fd4ff10608f4b26a6fd831263e27fa113e9528c4c3de4fa632bb4755f8b languageName: node linkType: hard From 88586543f9b17a992041e1c5b247be5ab23df9bd Mon Sep 17 00:00:00 2001 From: Ian <52504170+ibacher@users.noreply.github.com> Date: Thu, 29 Aug 2024 15:54:27 -0400 Subject: [PATCH 2/6] (fix) Move HtmlFormEntryForm type to patient-common-lib (#1983) --- .../visits-table/visits-table.component.tsx | 8 ++++++-- .../src/form-entry/form-entry.ts | 2 +- packages/esm-patient-common-lib/src/types/index.ts | 10 ++++++++-- packages/esm-patient-forms-app/src/config-schema.ts | 9 +-------- .../esm-patient-forms-app/src/form-entry-interop.ts | 9 ++++++--- .../src/offline-forms/offline-form-helpers.ts | 2 +- 6 files changed, 23 insertions(+), 17 deletions(-) diff --git a/packages/esm-patient-chart-app/src/visit/visits-widget/past-visits-components/visits-table/visits-table.component.tsx b/packages/esm-patient-chart-app/src/visit/visits-widget/past-visits-components/visits-table/visits-table.component.tsx index bfb97d1b47..140df9f298 100644 --- a/packages/esm-patient-chart-app/src/visit/visits-widget/past-visits-components/visits-table/visits-table.component.tsx +++ b/packages/esm-patient-chart-app/src/visit/visits-widget/past-visits-components/visits-table/visits-table.component.tsx @@ -37,8 +37,12 @@ import { useSession, userHasAccess, } from '@openmrs/esm-framework'; -import { EmptyState, PatientChartPagination, launchFormEntryOrHtmlForms } from '@openmrs/esm-patient-common-lib'; -import type { HtmlFormEntryForm } from '@openmrs/esm-patient-forms-app/src/config-schema'; +import { + type HtmlFormEntryForm, + EmptyState, + PatientChartPagination, + launchFormEntryOrHtmlForms, +} from '@openmrs/esm-patient-common-lib'; import { deleteEncounter } from './visits-table.resource'; import { type MappedEncounter } from '../../visit.resource'; import EncounterObservations from '../../encounter-observations'; diff --git a/packages/esm-patient-common-lib/src/form-entry/form-entry.ts b/packages/esm-patient-common-lib/src/form-entry/form-entry.ts index 0c918c0c59..4344381ef2 100644 --- a/packages/esm-patient-common-lib/src/form-entry/form-entry.ts +++ b/packages/esm-patient-common-lib/src/form-entry/form-entry.ts @@ -1,4 +1,4 @@ -import { type HtmlFormEntryForm } from '@openmrs/esm-patient-forms-app/src/config-schema'; +import { type HtmlFormEntryForm } from '../types'; export interface FormEntryProps { encounterUuid?: string; diff --git a/packages/esm-patient-common-lib/src/types/index.ts b/packages/esm-patient-common-lib/src/types/index.ts index 38c6a9ff36..7ef7db6a5a 100644 --- a/packages/esm-patient-common-lib/src/types/index.ts +++ b/packages/esm-patient-common-lib/src/types/index.ts @@ -1,5 +1,3 @@ -import { type OpenmrsResource } from '@openmrs/esm-framework'; - export * from './test-results'; export interface DashboardLinkConfig { @@ -49,3 +47,11 @@ export interface DisplayMetadata { links: Links; uuid: string; } + +export interface HtmlFormEntryForm { + formUuid: string; + formName: string; + formUiResource: string; + formUiPage: 'enterHtmlFormWithSimpleUi' | 'enterHtmlFormWithStandardUi'; + formEditUiPage: 'editHtmlFormWithSimpleUi' | 'editHtmlFormWithStandardUi'; +} diff --git a/packages/esm-patient-forms-app/src/config-schema.ts b/packages/esm-patient-forms-app/src/config-schema.ts index 655b82f343..00732e2ac0 100644 --- a/packages/esm-patient-forms-app/src/config-schema.ts +++ b/packages/esm-patient-forms-app/src/config-schema.ts @@ -1,4 +1,5 @@ import { validator, Type } from '@openmrs/esm-framework'; +import { type HtmlFormEntryForm } from '@openmrs/esm-patient-common-lib'; export const configSchema = { htmlFormEntryForms: { @@ -133,14 +134,6 @@ export const configSchema = { }, }; -export interface HtmlFormEntryForm { - formUuid: string; - formName: string; - formUiResource: string; - formUiPage: 'enterHtmlFormWithSimpleUi' | 'enterHtmlFormWithStandardUi'; - formEditUiPage: 'editHtmlFormWithSimpleUi' | 'editHtmlFormWithStandardUi'; -} - export interface FormsSection { name: string; forms: Array; diff --git a/packages/esm-patient-forms-app/src/form-entry-interop.ts b/packages/esm-patient-forms-app/src/form-entry-interop.ts index c8a1d2cd2f..db65784042 100644 --- a/packages/esm-patient-forms-app/src/form-entry-interop.ts +++ b/packages/esm-patient-forms-app/src/form-entry-interop.ts @@ -1,7 +1,10 @@ import { navigate, type Visit } from '@openmrs/esm-framework'; -import { type HtmlFormEntryForm } from './config-schema'; -import isEmpty from 'lodash-es/isEmpty'; -import { launchPatientWorkspace, launchStartVisitPrompt } from '@openmrs/esm-patient-common-lib'; +import { + type HtmlFormEntryForm, + launchPatientWorkspace, + launchStartVisitPrompt, +} from '@openmrs/esm-patient-common-lib'; +import { isEmpty } from 'lodash-es'; export function launchFormEntryOrHtmlForms( currentVisit: Visit | undefined, diff --git a/packages/esm-patient-forms-app/src/offline-forms/offline-form-helpers.ts b/packages/esm-patient-forms-app/src/offline-forms/offline-form-helpers.ts index 463983bed2..935513869b 100644 --- a/packages/esm-patient-forms-app/src/offline-forms/offline-form-helpers.ts +++ b/packages/esm-patient-forms-app/src/offline-forms/offline-form-helpers.ts @@ -1,7 +1,7 @@ import useSWR from 'swr'; import { getDynamicOfflineDataEntries } from '@openmrs/esm-framework'; +import { type HtmlFormEntryForm } from '@openmrs/esm-patient-common-lib'; import { type Form, type FormEncounterResource } from '../types'; -import { type HtmlFormEntryForm } from '../config-schema'; /** * Returns whether the given form encounter is valid for offline mode and can be cached. From 1eaec53fe993f5891f89819349224e1c5492ebe2 Mon Sep 17 00:00:00 2001 From: Vineet Sharma Date: Fri, 30 Aug 2024 14:33:47 +0530 Subject: [PATCH 3/6] (fix) O3-3622: Allow saving lab reference number with the lab order (#1968) * Replace instances of labReferenceNumber with accessionNumber * Update test for lab order form --------- Co-authored-by: Usama Idriss Kakumba <53287480+usamaidrsk@users.noreply.github.com> Co-authored-by: Dennis Kigen --- packages/esm-patient-common-lib/src/orders/types.ts | 8 +++++--- .../src/lab-orders/add-lab-order/add-lab-order.scss | 3 +-- .../src/lab-orders/add-lab-order/add-lab-order.test.tsx | 2 +- .../lab-orders/add-lab-order/lab-order-form.component.tsx | 8 ++++---- packages/esm-patient-labs-app/src/lab-orders/api.ts | 3 +++ packages/esm-patient-orders-app/src/utils/index.ts | 2 +- 6 files changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/esm-patient-common-lib/src/orders/types.ts b/packages/esm-patient-common-lib/src/orders/types.ts index 96911d6ff2..81478cd166 100644 --- a/packages/esm-patient-common-lib/src/orders/types.ts +++ b/packages/esm-patient-common-lib/src/orders/types.ts @@ -39,6 +39,10 @@ export interface OrderBasketItem { }; extractedOrderError?: ExtractedOrderErrorObject; isOrderIncomplete?: boolean; + /** + * An optional identifier from the fulfiller (e.g., lab system) for the specimen or record associated with the order. + */ + accessionNumber?: string; } export interface OrderPost { @@ -68,7 +72,7 @@ export interface OrderPost { orderReasonNonCoded?: string; orderReason?: string; instructions?: string; - labReferenceNumber?: string; + accessionNumber?: string; } export interface PatientOrderFetchResponse { @@ -146,7 +150,6 @@ export interface Order { clinicalHistory: string; numberOfRepeats: string; type: string; - labReferenceNumber?: string; } export interface OrderTypeFetchResponse { @@ -204,7 +207,6 @@ export interface LabOrderBasketItem extends OrderBasketItem { label: string; conceptUuid: string; }; - labReferenceNumber?: string; urgency?: string; instructions?: string; previousOrder?: string; diff --git a/packages/esm-patient-labs-app/src/lab-orders/add-lab-order/add-lab-order.scss b/packages/esm-patient-labs-app/src/lab-orders/add-lab-order/add-lab-order.scss index 66ed160487..bf2fc693c8 100644 --- a/packages/esm-patient-labs-app/src/lab-orders/add-lab-order/add-lab-order.scss +++ b/packages/esm-patient-labs-app/src/lab-orders/add-lab-order/add-lab-order.scss @@ -28,8 +28,7 @@ button { display: flex; - padding-left: 0 !important; - margin: 0 layout.$spacing-05; + margin-left: -(layout.$spacing-03); svg { order: 1; diff --git a/packages/esm-patient-labs-app/src/lab-orders/add-lab-order/add-lab-order.test.tsx b/packages/esm-patient-labs-app/src/lab-orders/add-lab-order/add-lab-order.test.tsx index b4ba8c3da6..db9e2bd2e5 100644 --- a/packages/esm-patient-labs-app/src/lab-orders/add-lab-order/add-lab-order.test.tsx +++ b/packages/esm-patient-labs-app/src/lab-orders/add-lab-order/add-lab-order.test.tsx @@ -139,7 +139,7 @@ describe('AddLabOrder', () => { display: 'CD4 COUNT', urgency: 'STAT', instructions: 'plz do it thx', - labReferenceNumber: 'lba-000124', + accessionNumber: 'lba-000124', testType: { label: 'CD4 COUNT', conceptUuid: 'test-lab-uuid-2' }, orderer: mockSessionDataResponse.data.currentProvider.uuid, }), diff --git a/packages/esm-patient-labs-app/src/lab-orders/add-lab-order/lab-order-form.component.tsx b/packages/esm-patient-labs-app/src/lab-orders/add-lab-order/lab-order-form.component.tsx index 5a50965d23..a1a057c5f2 100644 --- a/packages/esm-patient-labs-app/src/lab-orders/add-lab-order/lab-order-form.component.tsx +++ b/packages/esm-patient-labs-app/src/lab-orders/add-lab-order/lab-order-form.component.tsx @@ -62,7 +62,7 @@ export function LabOrderForm({ urgency: z.string().refine((value) => value !== '', { message: translateFrom(moduleName, 'addLabOrderPriorityRequired', 'Priority is required'), }), - labReferenceNumber: z.string().optional(), + accessionNumber: z.string().optional(), testType: z.object( { label: z.string(), conceptUuid: z.string() }, { @@ -182,7 +182,7 @@ export function LabOrderForm({ ( )} /> diff --git a/packages/esm-patient-labs-app/src/lab-orders/api.ts b/packages/esm-patient-labs-app/src/lab-orders/api.ts index 95e3996c93..391c6d9f36 100644 --- a/packages/esm-patient-labs-app/src/lab-orders/api.ts +++ b/packages/esm-patient-labs-app/src/lab-orders/api.ts @@ -92,6 +92,7 @@ export function prepLabOrderPostData( concept: order.testType.conceptUuid, instructions: order.instructions, orderReason: order.orderReason, + accessionNumber: order.accessionNumber, }; } else if (order.action === 'REVISE') { return { @@ -105,6 +106,7 @@ export function prepLabOrderPostData( instructions: order.instructions, orderReason: order.orderReason, previousOrder: order.previousOrder, + accessionNumber: order.accessionNumber, }; } else if (order.action === 'DISCONTINUE') { return { @@ -117,6 +119,7 @@ export function prepLabOrderPostData( concept: order.testType.conceptUuid, orderReason: order.orderReason, previousOrder: order.previousOrder, + accessionNumber: order.accessionNumber, }; } else { throw new Error(`Unknown order action: ${order.action}.`); diff --git a/packages/esm-patient-orders-app/src/utils/index.ts b/packages/esm-patient-orders-app/src/utils/index.ts index ec019258a9..1f8c556287 100644 --- a/packages/esm-patient-orders-app/src/utils/index.ts +++ b/packages/esm-patient-orders-app/src/utils/index.ts @@ -78,7 +78,7 @@ export function buildLabOrder(order: Order, action?: OrderAction) { careSetting: order.careSetting.uuid, instructions: order.instructions, urgency: order.urgency, - labReferenceNumber: order.labReferenceNumber, + accessionNumber: order.accessionNumber, testType: { label: order.concept.display, conceptUuid: order.concept.uuid, From 686aece97ab5ed0b89ac7e802ff0a62ce19b079c Mon Sep 17 00:00:00 2001 From: Dennis Kigen Date: Mon, 2 Sep 2024 19:20:03 +0300 Subject: [PATCH 4/6] (feat) Lab results form improvements (#1985) This PR makes the following improvements to the lab results form following the recent change to make it the single source of truth for lab orders: - Refactors the error notification shown when the user tries to submit the form without filling any fields to use the Carbon InlineNotification component - Refactors the logic used to determine whether the form is empty to leverage the getValues method of react-hook-form - Increases the Stack gap to 1rem to make the form more spacious - Removes error handling concerns from the form field component - Refactors the form field component to use the Carbon TextInput or NumberInput components based on the concept datatype - Renames the result form field component to lab results form field component --- ...x => lab-results-form-field.component.tsx} | 140 ++++++++++-------- .../lab-results-form.component.tsx | 54 ++++--- .../src/lab-results/lab-results-form.scss | 4 + .../translations/en.json | 2 +- 4 files changed, 114 insertions(+), 86 deletions(-) rename packages/esm-patient-orders-app/src/lab-results/{result-form-field.component.tsx => lab-results-form-field.component.tsx} (56%) diff --git a/packages/esm-patient-orders-app/src/lab-results/result-form-field.component.tsx b/packages/esm-patient-orders-app/src/lab-results/lab-results-form-field.component.tsx similarity index 56% rename from packages/esm-patient-orders-app/src/lab-results/result-form-field.component.tsx rename to packages/esm-patient-orders-app/src/lab-results/lab-results-form-field.component.tsx index 38c968decf..52f6dbb0e6 100644 --- a/packages/esm-patient-orders-app/src/lab-results/result-form-field.component.tsx +++ b/packages/esm-patient-orders-app/src/lab-results/lab-results-form-field.component.tsx @@ -1,29 +1,29 @@ import React from 'react'; -import { TextInput, Select, SelectItem } from '@carbon/react'; +import { NumberInput, Select, SelectItem, TextInput } from '@carbon/react'; import { useTranslation } from 'react-i18next'; -import { Controller } from 'react-hook-form'; +import { type Control, Controller } from 'react-hook-form'; import { type LabOrderConcept } from './lab-results.resource'; import styles from './lab-results-form.scss'; interface ResultFormFieldProps { - defaultValue: any; concept: LabOrderConcept; - control: any; - register: any; - errors?: any; + control: Control; + defaultValue: any; } -const ResultFormField: React.FC = ({ defaultValue, register, concept, control, errors }) => { +const ResultFormField: React.FC = ({ concept, control, defaultValue }) => { const { t } = useTranslation(); - const isTextOrNumeric = (concept) => concept.datatype?.display === 'Text' || concept.datatype?.display === 'Numeric'; - const isCoded = (concept) => concept.datatype?.display === 'Coded'; - const isPanel = (concept) => concept.setMembers?.length > 0; + + const isCoded = (concept: LabOrderConcept) => concept.datatype?.display === 'Coded'; + const isNumeric = (concept: LabOrderConcept) => concept.datatype?.display === 'Numeric'; + const isPanel = (concept: LabOrderConcept) => concept.setMembers?.length > 0; + const isText = (concept: LabOrderConcept) => concept.datatype?.display === 'Text'; const printValueRange = (concept: LabOrderConcept) => { if (concept?.datatype?.display === 'Numeric') { const maxVal = Math.max(concept?.hiAbsolute, concept?.hiCritical, concept?.hiNormal); const minVal = Math.min(concept?.lowAbsolute, concept?.lowCritical, concept?.lowNormal); - return `(${minVal ?? 0} - ${maxVal > 0 ? maxVal : '--'} ${concept?.units ?? ''})`; + return ` (${minVal ?? 0} - ${maxVal > 0 ? maxVal : '--'} ${concept?.units ?? ''})`; } return ''; }; @@ -36,25 +36,38 @@ const ResultFormField: React.FC = ({ defaultValue, registe return ( <> - {Object.keys(errors).length > 0 &&
All fields are required
} - {isTextOrNumeric(concept) && ( + {isText(concept) && ( ( + )} + /> + )} + + {isNumeric(concept) && ( + ( + field.onChange(event.target.value)} + value={field.value || ''} /> )} /> @@ -64,18 +77,14 @@ const ResultFormField: React.FC = ({ defaultValue, registe ( @@ -144,9 +160,9 @@ const ResultFormField: React.FC = ({ defaultValue, registe )} /> - ); - } - })} + )} + + ))} ); }; diff --git a/packages/esm-patient-orders-app/src/lab-results/lab-results-form.component.tsx b/packages/esm-patient-orders-app/src/lab-results/lab-results-form.component.tsx index 599e1c51fb..4632389dd0 100644 --- a/packages/esm-patient-orders-app/src/lab-results/lab-results-form.component.tsx +++ b/packages/esm-patient-orders-app/src/lab-results/lab-results-form.component.tsx @@ -1,22 +1,22 @@ import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useForm } from 'react-hook-form'; -import { restBaseUrl, showSnackbar, useAbortController, useLayoutType } from '@openmrs/esm-framework'; -import { Button, ButtonSet, Form, InlineLoading, Stack } from '@carbon/react'; +import { mutate } from 'swr'; +import { Button, ButtonSet, Form, InlineLoading, InlineNotification, Stack } from '@carbon/react'; import { type DefaultPatientWorkspaceProps, type Order } from '@openmrs/esm-patient-common-lib'; +import { restBaseUrl, showSnackbar, useAbortController, useLayoutType } from '@openmrs/esm-framework'; import { useOrderConceptByUuid, updateOrderResult, useLabEncounter, useObservation } from './lab-results.resource'; -import ResultFormField from './result-form-field.component'; +import ResultFormField from './lab-results-form-field.component'; import styles from './lab-results-form.scss'; -import { mutate } from 'swr'; export interface LabResultsFormProps extends DefaultPatientWorkspaceProps { order: Order; } const LabResultsForm: React.FC = ({ - order, closeWorkspace, closeWorkspaceWithSavedChanges, + order, promptBeforeClosing, }) => { const { t } = useTranslation(); @@ -24,17 +24,17 @@ const LabResultsForm: React.FC = ({ const isTablet = useLayoutType() === 'tablet'; const [obsUuid, setObsUuid] = useState(''); const [isEditing, setIsEditing] = useState(false); - const [isSubmitting, setIsSubmitting] = useState(false); const [initialValues, setInitialValues] = useState(null); const [isLoadingInitialValues, setIsLoadingInitialValues] = useState(false); const { concept, isLoading: isLoadingConcepts } = useOrderConceptByUuid(order.concept.uuid); const { encounter, isLoading: isLoadingEncounter, mutate: mutateLabOrders } = useLabEncounter(order.encounter.uuid); const { data, isLoading: isLoadingObs, error: isErrorObs } = useObservation(obsUuid); + const [showEmptyFormErrorNotification, setShowEmptyFormErrorNotification] = useState(false); const { control, register, - formState: { errors, isDirty }, + formState: { errors, isDirty, isSubmitting }, getValues, handleSubmit, } = useForm<{ testResult: any }>({ @@ -82,11 +82,18 @@ const LabResultsForm: React.FC = ({ ); } - const saveLabResults = (data, e) => { - setIsSubmitting(true); - e.preventDefault(); - // assign result to test order - const documentedValues = getValues(); + const saveLabResults = () => { + const formValues = getValues(); + + const isEmptyForm = Object.values(formValues).every( + (value) => value === '' || value === null || value === undefined, + ); + + if (isEmptyForm) { + setShowEmptyFormErrorNotification(true); + return; + } + let obsValue = []; if (concept.set && concept.setMembers.length > 0) { @@ -160,7 +167,6 @@ const LabResultsForm: React.FC = ({ abortController, ).then( () => { - setIsSubmitting(false); closeWorkspaceWithSavedChanges(); mutateLabOrders(); mutate( @@ -177,7 +183,6 @@ const LabResultsForm: React.FC = ({ }); }, (err) => { - setIsSubmitting(false); showSnackbar({ title: t('errorSavingLabResults', 'Error saving lab results'), kind: 'error', @@ -185,6 +190,8 @@ const LabResultsForm: React.FC = ({ }); }, ); + + setShowEmptyFormErrorNotification(false); }; return ( @@ -192,22 +199,23 @@ const LabResultsForm: React.FC = ({
{concept.setMembers.length > 0 &&

{concept.display}

} {concept && ( - + {!isLoadingInitialValues ? ( - + ) : ( )} )} + {showEmptyFormErrorNotification && ( + + )}
- - - - - - ) : ( -
- setSearchTerm(evt.target.value)} /> - -
- ))} +
{treeDataFiltered?.length > 0 ? ( treeDataFiltered?.map((root, index) => ( -
- +
+
)) ) : ( @@ -91,11 +61,48 @@ const FilterSet: React.FC = ({ hideFilterSetHeader = false }) => ); }; +const FilterNodeParent = ({ root, itemNumber }: filterNodeParentProps) => { + const config = useConfig(); + const { t } = useTranslation(); + const tablet = useLayoutType() === 'tablet'; + const [expandAll, setExpandAll] = useState(undefined); + + if (!root.subSets) return; + + const filterParent = root.subSets.map((node) => { + return ( + + ); + }); + + return ( +
+
+
{t(root.display)}
+ +
+ {filterParent} +
+ ); +}; + const FilterNode = ({ root, level, open }: FilterNodeProps) => { const tablet = useLayoutType() === 'tablet'; const { checkboxes, parents, updateParent } = useContext(FilterContext); const indeterminate = isIndeterminate(parents[root.flatName], checkboxes); const allChildrenChecked = parents[root.flatName]?.every((kid) => checkboxes[kid]); + return ( { id={root?.flatName} checked={root.hasData && allChildrenChecked} indeterminate={indeterminate} - labelText={`${root?.display} (${parents?.[root?.flatName]?.length})`} + labelText={`${root?.display} (${parents?.[root?.flatName]?.length ?? 0})`} onChange={() => updateParent(root.flatName)} disabled={!root.hasData} /> diff --git a/packages/esm-patient-labs-app/src/test-results/filter/filter-set.scss b/packages/esm-patient-labs-app/src/test-results/filter/filter-set.scss index c2a8120531..106465cb95 100644 --- a/packages/esm-patient-labs-app/src/test-results/filter/filter-set.scss +++ b/packages/esm-patient-labs-app/src/test-results/filter/filter-set.scss @@ -1,61 +1,53 @@ @use '@carbon/layout'; -@use '@carbon/type'; -@use '@openmrs/esm-styleguide/src/vars' as *; +@use '@carbon/styles/scss/type'; +@use '@carbon/colors'; +@import '@openmrs/esm-styleguide/src/vars'; -.stickyFilterSet { - position: sticky; - top: 6.5rem; - overflow-y: hidden; +.filterSetContent { + max-height: calc(100vh - 9.5rem); + overflow-y: auto; } -.filterSetHeader { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: layout.$spacing-03; +// background of filter, and spacing between containers +.nestedAccordion { background-color: $openmrs-background-grey; - position: sticky; - top: 0; - z-index: 1; + margin: layout.$spacing-02 0; + + @media (min-width: $breakpoint-small-desktop-min) { + margin: layout.$spacing-02 0; + } - h4 { - @include type.type-style('heading-compact-02'); - color: $text-02; + :global(.cds--accordion__item) { + border: none; } - .filterSetActions { - display: flex; - justify-content: flex-end; - align-items: center; + :global(.cds--accordion__item:last-child) { + border: none; } } -.filterTreeSearchHeader { +.treeNodeHeader { display: flex; + justify-content: space-between; + border-bottom: 1px solid colors.$gray-20; + padding: layout.$spacing-03 0; margin-bottom: layout.$spacing-03; - background-color: $openmrs-background-grey; - position: sticky; - top: 0; - z-index: 1; + align-items: center; } -.filterSetContent { - max-height: calc(100vh - 9.5rem); - overflow-y: auto; +.treeNodeHeaderTablet { + padding-left: layout.$spacing-05; + margin-bottom: 0; } -// background of filter, and spacing between containers -.nestedAccordion { - background-color: $openmrs-background-grey; - margin: layout.$spacing-02 0; - @media (min-width: $breakpoint-small-desktop-min) { - background-color: $ui-background; - } +.nestedAccordionTablet { + margin-bottom: layout.$spacing-05; } // our special accordion rules .nestedAccordion > :global(.cds--accordion--start) > :global(.cds--accordion__item--active) { border-left: 0.375rem solid var(--brand-01); + @media (max-width: $breakpoint-tablet-max) { margin: layout.$spacing-06 0; } @@ -75,6 +67,7 @@ .cds--accordion__item--active > .cds--accordion__content { display: block; + padding-right: layout.$spacing-05; } .cds--accordion__title { diff --git a/packages/esm-patient-labs-app/src/test-results/grouped-timeline/grouped-timeline.component.tsx b/packages/esm-patient-labs-app/src/test-results/grouped-timeline/grouped-timeline.component.tsx index f131a061c7..2d6fa7e298 100644 --- a/packages/esm-patient-labs-app/src/test-results/grouped-timeline/grouped-timeline.component.tsx +++ b/packages/esm-patient-labs-app/src/test-results/grouped-timeline/grouped-timeline.component.tsx @@ -2,7 +2,7 @@ import React, { useCallback, useContext, useEffect, useRef, useState } from 'rea import classNames from 'classnames'; import { useTranslation } from 'react-i18next'; import { EmptyState } from '@openmrs/esm-patient-common-lib'; -import { ConfigurableLink, usePatient } from '@openmrs/esm-framework'; +import { ConfigurableLink, showModal, usePatient } from '@openmrs/esm-framework'; import { Grid, ShadowBox } from '../panel-timeline/helpers'; import { makeThrottled, testResultsBasePath } from '../helpers'; import type { @@ -30,6 +30,14 @@ const PanelNameCorner: React.FC = ({ showShadow, panelName const NewRowStartCell = ({ title, range, units, conceptUuid, shadow = false, isString = false }) => { const { patientUuid } = usePatient(); + const launchResultsDialog = (patientUuid: string, title: string, testUuid: string) => { + const dispose = showModal('timeline-results-modal', { + closeDeleteModal: () => dispose(), + patientUuid, + testUuid, + title, + }); + }; return (
- {!isString ? ( - - {title} - - ) : ( - {title} - )} + + {!isString ? ( + launchResultsDialog(patientUuid, title, conceptUuid)} + > + {title} + + ) : ( + {title} + )} + {range} {units} @@ -292,18 +302,19 @@ export const GroupedTimeline = () => { ) : rowData?.filter((row: { flatName: string }) => parents[parent.flatName].includes(row.flatName)); - // show kid rows return ( - + subRows?.length > 0 && ( + + ) ); } else return null; })} diff --git a/packages/esm-patient-labs-app/src/test-results/grouped-timeline/grouped-timeline.scss b/packages/esm-patient-labs-app/src/test-results/grouped-timeline/grouped-timeline.scss index 0cb5191eb7..33ed799b42 100644 --- a/packages/esm-patient-labs-app/src/test-results/grouped-timeline/grouped-timeline.scss +++ b/packages/esm-patient-labs-app/src/test-results/grouped-timeline/grouped-timeline.scss @@ -98,6 +98,7 @@ align-self: flex-start; background-color: $ui-03; padding: 0.6rem layout.$spacing-05; + border-top: 1px solid colors.$gray-30; } .rowStartCell { @@ -237,3 +238,13 @@ grid-row: 2 / -1; grid-column: 1 / 2; } + +.trendline-link-view { + @include type.type-style('helper-text-01'); + color: colors.$blue-60; + cursor: pointer; +} + +.trendline-link-view:hover { + text-decoration: underline; +} diff --git a/packages/esm-patient-labs-app/src/test-results/individual-results-table/individual-results-table.component.tsx b/packages/esm-patient-labs-app/src/test-results/individual-results-table/individual-results-table.component.tsx new file mode 100644 index 0000000000..a38ca4ab89 --- /dev/null +++ b/packages/esm-patient-labs-app/src/test-results/individual-results-table/individual-results-table.component.tsx @@ -0,0 +1,167 @@ +import React, { useMemo } from 'react'; +import classNames from 'classnames'; +import { useTranslation } from 'react-i18next'; +import { + DataTableSkeleton, + Button, + DataTable, + TableContainer, + Table, + TableHead, + TableRow, + TableHeader, + TableBody, + TableCell, +} from '@carbon/react'; +import { ArrowRight } from '@carbon/react/icons'; +import { showModal, useLayoutType, isDesktop, formatDate } from '@openmrs/esm-framework'; +import { getPatientUuidFromUrl, type OBSERVATION_INTERPRETATION } from '@openmrs/esm-patient-common-lib'; +import styles from './individual-results-table.scss'; + +const IndividualResultsTable = ({ isLoading, parent, subRows, index }) => { + const { t } = useTranslation(); + const layout = useLayoutType(); + const patientUuid = getPatientUuidFromUrl(); + + const headerTitle = t(parent.display); + + const launchResultsDialog = (patientUuid: string, title: string, testUuid: string) => { + const dispose = showModal('timeline-results-modal', { + closeDeleteModal: () => dispose(), + patientUuid, + testUuid, + title, + }); + }; + + const tableHeaders = [ + { key: 'testName', header: t('testName', 'Test Name') }, + { + key: 'value', + header: t('value', 'Value'), + }, + { key: 'referenceRange', header: t('referenceRange', 'Reference range') }, + ]; + + const tableRows = useMemo(() => { + const rowData = subRows.flatMap((row, i) => { + const { units = '', range = '', obs: values } = row; + const isString = isNaN(parseFloat(values?.[0]?.value)); + + return { + ...row, + id: `${i}-${index}`, + testName: ( + + {!isString ? ( + launchResultsDialog(patientUuid, row.display, row.conceptUuid)} + > + {row.display} + + ) : ( + {row.display} + )} + + ), + value: { + value: (row.obs[0]?.value ? row.obs[0]?.value : '') + +' ' + (row?.units ? ` ${row?.units}` : ''), + interpretation: row.obs[0]?.interpretation, + }, + referenceRange: `${range || '--'} ${units || '--'}`, + }; + }); + + return rowData; + }, [index, patientUuid, subRows]); + + if (isLoading) return ; + if (subRows?.length) { + return ( +
+ + {({ rows, headers, getHeaderProps, getTableProps }) => ( + +
+

{headerTitle}

+
+ + {subRows[0]?.obs[0]?.obsDatetime + ? formatDate(new Date(subRows[0]?.obs[0]?.obsDatetime), { mode: 'standard' }) + : ''} + + +
+
+ + + + {headers.map((header) => ( + {header.header} + ))} + + + + {rows.map((row) => { + return ( + + {row.cells.map((cell) => + cell?.value?.interpretation ? ( + +

{cell?.value?.value ?? cell?.value}

+
+ ) : ( + +

{cell?.value}

+
+ ), + )} +
+ ); + })} +
+
+
+ )} +
+
+ ); + } +}; + +export default IndividualResultsTable; + +export const getClasses = (interpretation: OBSERVATION_INTERPRETATION) => { + switch (interpretation) { + case 'OFF_SCALE_HIGH': + return styles['off-scale-high']; + + case 'CRITICALLY_HIGH': + return styles['critically-high']; + + case 'HIGH': + return styles['high']; + + case 'OFF_SCALE_LOW': + return styles['off-scale-low']; + + case 'CRITICALLY_LOW': + return styles['critically-low']; + + case 'LOW': + return styles['low']; + + case 'NORMAL': + default: + return ''; + } +}; diff --git a/packages/esm-patient-labs-app/src/test-results/individual-results-table/individual-results-table.scss b/packages/esm-patient-labs-app/src/test-results/individual-results-table/individual-results-table.scss new file mode 100644 index 0000000000..4bbae0eadb --- /dev/null +++ b/packages/esm-patient-labs-app/src/test-results/individual-results-table/individual-results-table.scss @@ -0,0 +1,137 @@ +@use '@carbon/layout'; +@use '@carbon/colors'; +@use '@carbon/type'; +@use '@openmrs/esm-styleguide/src/vars' as *; + +.off-scale-high, +.off-scale-low, +.critically-high, +.critically-low, +.high, +.low { + p { + @include type.type-style('heading-compact-01'); + } +} + +.high, +.low { + background-color: colors.$orange-10 !important; + border-top: 1px solid colors.$orange-20 !important; +} + +.critically-high, +.critically-low { + background-color: colors.$red-20 !important; + border-top: 1px solid colors.$red-20-hover !important; +} + +.off-scale-low { + p::after { + content: ' ↓↓↓'; + } +} + +.off-scale-high { + p::after { + content: ' ↑↑↑'; + } +} + +.critically-low { + p::after { + content: ' ↓↓'; + } +} + +.critically-high { + p::after { + content: ' ↑↑'; + } +} + +.low { + p::after { + content: ' ↓'; + } +} + +.high { + p::after { + content: ' ↑'; + } +} + +.trendline-link-view { + color: colors.$blue-60; + cursor: pointer; +} + +.trendline-link-view:hover { + text-decoration: underline; +} + +.resultType { + @include type.type-style('heading-compact-02'); + color: $text-02; + margin-bottom: 5px; +} + +.date { + color: $text-02; + padding-right: 0.625rem; +} + +.date::after { + content: ''; + display: block; + width: layout.$spacing-07; + padding-top: 0.188rem; + border-bottom: 0.375rem solid var(--brand-03); +} + +.displayFlex { + display: flex; + flex-direction: row; + justify-content: flex-start; +} + +.viewTimeline { + display: flex; + margin-left: auto; +} + +.cardTitle { + background-color: white; + padding: 0.5rem; +} + +.tableContainer { + padding: 0; + + :global(.cds--data-table-content) { + border: 1px solid $ui-03; + border-bottom: none; + overflow: visible; + } + + :global(.cds--data-table-header) { + padding: 0; + } + + :global(.cds--table-toolbar) { + position: relative; + height: layout.$spacing-07; + overflow: visible; + top: 0; + } + + &:global(.cds--data-table-container) { + background: none !important; + } + + :global(.cds--toolbar-content) { + height: layout.$spacing-05; + margin-bottom: layout.$spacing-02; + } +} diff --git a/packages/esm-patient-labs-app/src/test-results/panel-timeline/helpers.tsx b/packages/esm-patient-labs-app/src/test-results/panel-timeline/helpers.tsx index 9b47014ace..3a3a8f0c89 100644 --- a/packages/esm-patient-labs-app/src/test-results/panel-timeline/helpers.tsx +++ b/packages/esm-patient-labs-app/src/test-results/panel-timeline/helpers.tsx @@ -36,16 +36,24 @@ export const Grid: React.FC<{ style: React.CSSProperties; padding?: boolean; dataColumns: number; -}> = ({ dataColumns, style = {}, padding = false, ...props }) => ( -
-); +}> = ({ dataColumns, style = {}, padding = false, ...props }) => { + const minColumnWidth = 4; + const maxColumnWidth = 10; + + const dynamicColumnWidth = Math.max(minColumnWidth, Math.min(maxColumnWidth, 100 / dataColumns)); + + return ( +
+ ); +}; export const PaddingContainer = React.forwardRef((props, ref) => (
diff --git a/packages/esm-patient-labs-app/src/test-results/panel-timeline/timeline.component.tsx b/packages/esm-patient-labs-app/src/test-results/panel-timeline/timeline.component.tsx index 5d75d2c741..92d5541a80 100644 --- a/packages/esm-patient-labs-app/src/test-results/panel-timeline/timeline.component.tsx +++ b/packages/esm-patient-labs-app/src/test-results/panel-timeline/timeline.component.tsx @@ -1,11 +1,11 @@ import React from 'react'; import classNames from 'classnames'; -import { EmptyState } from '@openmrs/esm-patient-common-lib'; import { PaddingContainer, TimeSlots, Grid, RowStartCell, GridItems, ShadowBox } from './helpers'; import { type ParsedTimeType } from '../filter/filter-types'; import type { ObsRecord } from '../../types'; import useScrollIndicator from './useScroll'; import styles from './timeline.scss'; +import { EmptyState } from '@openmrs/esm-patient-common-lib'; interface PanelNameCornerProps { showShadow: boolean; diff --git a/packages/esm-patient-labs-app/src/test-results/panel-timeline/timeline.scss b/packages/esm-patient-labs-app/src/test-results/panel-timeline/timeline.scss index ec0b385e6e..16178351d1 100644 --- a/packages/esm-patient-labs-app/src/test-results/panel-timeline/timeline.scss +++ b/packages/esm-patient-labs-app/src/test-results/panel-timeline/timeline.scss @@ -11,6 +11,11 @@ gap: 1px; justify-items: center; align-items: center; + border: 0.5px solid colors.$gray-30; +} + +.grid:hover { + border: none; } .day-column, @@ -61,7 +66,6 @@ left: 0; display: grid; grid-auto-flow: row; - gap: 0.5px; justify-items: baseline; align-items: center; padding: layout.$spacing-05; @@ -77,9 +81,7 @@ display: grid; grid-auto-flow: row; - column-gap: 1px; row-gap: 0px; - background-color: $color-gray-30; grid-template: auto auto / 9em auto; border-collapse: collapse; } @@ -109,6 +111,7 @@ left: 0px; top: 0px; z-index: 3; + border-right: 0.5px solid colors.$gray-30; span { @include type.type-style('heading-compact-01'); @@ -197,10 +200,8 @@ .recent-results-grid { display: grid; - background-color: white; overflow: auto; max-height: calc(100vh - 9rem); - border: 1px solid colors.$gray-50; } .recent-results-grid::-webkit-scrollbar { @@ -216,3 +217,4 @@ .trendline-link-view:hover { text-decoration: underline; } + diff --git a/packages/esm-patient-labs-app/src/test-results/results-viewer/results-viewer.extension.tsx b/packages/esm-patient-labs-app/src/test-results/results-viewer/results-viewer.extension.tsx index 929a91616d..7d5efdf71e 100644 --- a/packages/esm-patient-labs-app/src/test-results/results-viewer/results-viewer.extension.tsx +++ b/packages/esm-patient-labs-app/src/test-results/results-viewer/results-viewer.extension.tsx @@ -3,9 +3,8 @@ import classNames from 'classnames'; import { type TFunction, useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom'; import { ContentSwitcher, Switch, Button } from '@carbon/react'; -import { Printer, Renew } from '@carbon/react/icons'; import { EmptyState, ErrorState } from '@openmrs/esm-patient-common-lib'; -import { navigate, showModal, useConfig, useLayoutType } from '@openmrs/esm-framework'; +import { navigate, useConfig, useLayoutType } from '@openmrs/esm-framework'; import { FilterContext, FilterProvider } from '../filter'; import { useGetManyObstreeData } from '../grouped-timeline'; import { testResultsBasePath } from '../helpers'; @@ -15,9 +14,9 @@ import TreeViewWrapper from '../tree-view/tree-view-wrapper.component'; import Trendline from '../trendline/trendline.component'; import type { ConfigObject } from '../../config-schema'; import styles from './results-viewer.scss'; +import { type viewOpts } from '../../types'; type panelOpts = 'tree' | 'panel'; -type viewOpts = 'split' | 'full'; interface RefreshDataButtonProps { isTablet: boolean; @@ -59,14 +58,12 @@ const RoutedResultsViewer: React.FC = ({ basePath, patientUu const ResultsViewer: React.FC = ({ patientUuid, basePath, loading }) => { const { t } = useTranslation(); const isTablet = useLayoutType() === 'tablet'; - const [view, setView] = useState('split'); - const config = useConfig() as ConfigObject; + const [view, setView] = useState('individual-test'); const [selectedSection, setSelectedSection] = useState('tree'); - const { totalResultsCount } = useContext(FilterContext); + const { totalResultsCount, resetTree } = useContext(FilterContext); const { type, testUuid } = useParams(); const isExpanded = view === 'full'; const trendlineView = testUuid && type === 'trendline'; - const showPrintButton = config.showPrintButton; const responsiveSize = isTablet ? 'lg' : 'md'; const navigateBackFromTrendlineView = useCallback(() => { @@ -75,13 +72,6 @@ const ResultsViewer: React.FC = ({ patientUuid, basePath, lo }); }, [patientUuid]); - const openPrintModal = useCallback(() => { - const dispose = showModal('print-modal', { - patientUuid, - closeDialog: () => dispose(), - }); - }, [patientUuid]); - if (isTablet) { return (
@@ -94,11 +84,10 @@ const ResultsViewer: React.FC = ({ patientUuid, basePath, lo selectedIndex={['panel', 'tree'].indexOf(selectedSection)} onChange={({ name }: { name: panelOpts }) => setSelectedSection(name)} > - - + +
-
{selectedSection === 'tree' ? ( = ({ patientUuid, basePath, lo type={type} expanded={isExpanded} testUuid={testUuid} + view={view} /> ) : selectedSection === 'panel' ? ( = ({ patientUuid, basePath, lo
-

{`${t('results', 'Results')} ${ - totalResultsCount ? `(${totalResultsCount})` : '' - }`}

-
- setSelectedSection(name)} - > - - - - {showPrintButton && ( - - )} -
+

{t('tests', 'Tests')}

+
+

{`${t('results', 'Results')} ${ + totalResultsCount ? `(${totalResultsCount})` : '' + }`}

setView(name)} selectedIndex={isExpanded ? 1 : 0} size={responsiveSize} > - - + +
-
- {selectedSection === 'tree' ? ( - - ) : selectedSection === 'panel' ? ( - - ) : null} +
); }; -function RefreshDataButton({ isTablet, t }: RefreshDataButtonProps) { - return ( - - ); -} - export default RoutedResultsViewer; diff --git a/packages/esm-patient-labs-app/src/test-results/results-viewer/results-viewer.scss b/packages/esm-patient-labs-app/src/test-results/results-viewer/results-viewer.scss index e23744be84..ab992049f0 100644 --- a/packages/esm-patient-labs-app/src/test-results/results-viewer/results-viewer.scss +++ b/packages/esm-patient-labs-app/src/test-results/results-viewer/results-viewer.scss @@ -2,6 +2,24 @@ @use '@carbon/type'; @use '@openmrs/esm-styleguide/src/vars' as *; +:global(.omrs-breakpoint-gt-small-desktop .-esm-patient-chart__patient-chart__widthContained___Ow0JH) { + max-width: 150rem; +} + +@media (min-width: 1200px) { + :global(.-esm-patient-chart__patient-chart__widthContained___Ow0JH) { + max-width: 150rem; + } +} + +:global(.cds--data-table td, .cds--data-table th) { + vertical-align: top; +} + +:global(.cds--data-table--md td) { + padding-top: 0.1rem; +} + .resultsContainer { padding: 0; width: 100%; @@ -33,34 +51,39 @@ .leftHeaderSection { display: flex; align-items: center; + justify-content: space-between; } .viewOptsContentSwitcherContainer { - margin-left: auto; + display: grid; + grid-template-columns: 2fr 1fr; + justify-content: space-between; +} + +.viewOptionsText { + margin-top: layout.$spacing-03; + justify-self: start; } .viewOptionsSwitcher { - max-width: 10rem; + text-align: right; + justify-self: end; } .leftSection { - width: 45%; + width: 350px; margin-right: layout.$spacing-05; } .rightSection, .rightSectionHeader { - width: 55%; + width: 70%; &.fullView { width: 100%; } } -.rightSectionHeader { - display: flex; -} - .rightSection :global(.cds--skeleton) { width: 100%; } diff --git a/packages/esm-patient-labs-app/src/test-results/tree-view/tree-view-wrapper.component.tsx b/packages/esm-patient-labs-app/src/test-results/tree-view/tree-view-wrapper.component.tsx index b1a0bf7ff3..31219a1e2b 100644 --- a/packages/esm-patient-labs-app/src/test-results/tree-view/tree-view-wrapper.component.tsx +++ b/packages/esm-patient-labs-app/src/test-results/tree-view/tree-view-wrapper.component.tsx @@ -5,6 +5,7 @@ import { FilterProvider } from '../filter/filter-context'; import TreeView from './tree-view.component'; import { useConfig } from '@openmrs/esm-framework'; import { useGetManyObstreeData } from '../grouped-timeline'; +import { type viewOpts } from '../../types'; interface TreeViewWrapperProps { patientUuid: string; @@ -12,6 +13,7 @@ interface TreeViewWrapperProps { testUuid: string; expanded: boolean; type: string; + view?: viewOpts; } const TreeViewWrapper: React.FC = (props) => { diff --git a/packages/esm-patient-labs-app/src/test-results/tree-view/tree-view.component.tsx b/packages/esm-patient-labs-app/src/test-results/tree-view/tree-view.component.tsx index 67f0653a44..4918329f70 100644 --- a/packages/esm-patient-labs-app/src/test-results/tree-view/tree-view.component.tsx +++ b/packages/esm-patient-labs-app/src/test-results/tree-view/tree-view.component.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import classNames from 'classnames'; import { useTranslation } from 'react-i18next'; import { AccordionSkeleton, DataTableSkeleton, Button } from '@carbon/react'; @@ -6,11 +6,13 @@ import { TreeViewAlt } from '@carbon/react/icons'; import { useLayoutType } from '@openmrs/esm-framework'; import FilterSet, { FilterContext } from '../filter'; import GroupedTimeline from '../grouped-timeline'; -import PanelTimelineComponent from '../panel-timeline/panel-timeline-component'; import TabletOverlay from '../tablet-overlay'; import Trendline from '../trendline/trendline.component'; import usePanelData from '../panel-view/usePanelData'; import styles from '../results-viewer/results-viewer.scss'; +import { type viewOpts } from '../../types'; +import IndividualResultsTable from '../individual-results-table/individual-results-table.component'; +import { EmptyState } from '@openmrs/esm-patient-common-lib'; interface TreeViewProps { patientUuid: string; @@ -19,9 +21,52 @@ interface TreeViewProps { loading: boolean; expanded: boolean; type: string; + view?: viewOpts; } -const TreeView: React.FC = ({ patientUuid, basePath, testUuid, loading, expanded, type }) => { +const GroupedPanelsTables = ({ loadingPanelData }) => { + const { timelineData, parents, checkboxes, someChecked, lowestParents } = useContext(FilterContext); + const [panelName, setPanelName] = useState(''); + const { t } = useTranslation(); + let shownGroups = 0; + + const { + data: { rowData }, + } = timelineData; + + useEffect(() => { + setPanelName(''); + }, [rowData]); + + const filteredParents = lowestParents?.filter( + (parent) => parents[parent.flatName].some((kid) => checkboxes[kid]) || !someChecked, + ); + + if (rowData && rowData?.length === 0) { + return ; + } + + return ( + <> + {filteredParents?.map((parent, index) => { + shownGroups += 1; + const subRows = someChecked + ? rowData?.filter( + (row: { flatName: string }) => + parents[parent.flatName].includes(row.flatName) && checkboxes[row.flatName], + ) + : rowData?.filter((row: { flatName: string }) => parents[parent.flatName].includes(row.flatName)); + return ( +
+ +
+ ); + })} + + ); +}; + +const TreeView: React.FC = ({ patientUuid, basePath, testUuid, loading, expanded, type, view }) => { const tablet = useLayoutType() === 'tablet'; const [showTreeOverlay, setShowTreeOverlay] = useState(false); const { t } = useTranslation(); @@ -77,17 +122,17 @@ const TreeView: React.FC = ({ patientUuid, basePath, testUuid, lo ) : loading || isLoadingPanelData ? ( - ) : someChecked ? ( - - ) : ( - // If no filter is selected from the filter view - // All the test results recorded for the patient needs to be shown + ) : view === 'individual-test' ? ( +
+ +
+ ) : view === 'over-time' ? ( panels.map((panel) => (
- +
)) - )} + ) : null}
); diff --git a/packages/esm-patient-labs-app/src/types.ts b/packages/esm-patient-labs-app/src/types.ts index ad6e5f42a6..5b66651cbd 100644 --- a/packages/esm-patient-labs-app/src/types.ts +++ b/packages/esm-patient-labs-app/src/types.ts @@ -132,3 +132,5 @@ export interface ObservationSet { uuid: string; meta: ConceptMeta; } + +export type viewOpts = 'individual-test' | 'over-time' | 'full'; diff --git a/packages/esm-patient-labs-app/translations/en.json b/packages/esm-patient-labs-app/translations/en.json index 7590a896c6..855a148ee6 100644 --- a/packages/esm-patient-labs-app/translations/en.json +++ b/packages/esm-patient-labs-app/translations/en.json @@ -23,9 +23,9 @@ "errorFetchingTestTypes": "Error fetching results for \"{{searchTerm}}\"", "errorLoadingTestTypes": "Error occured when loading test types", "female": "Female", - "full": "Full", "goToDrugOrderForm": "Order form", "hideResultsTable": "Hide results table", + "individualTests": "Individual tests", "labOrders": "Lab orders", "labReferenceNumber": "Lab reference number", "male": "Male", @@ -44,6 +44,7 @@ "ordered": "Ordered", "orderReason": "Order reason", "other": "Other", + "overTime": "Over time", "panel": "Panel", "panels": "panels", "pleaseRequiredFields": "Please fill all required fields", @@ -54,8 +55,8 @@ "recentResults": "Recent Results", "recentTestResults": "recent test results", "referenceRange": "Reference range", - "refreshData": "Refresh data", "removeFromBasket": "Remove from basket", + "reset": "Reset", "resetTreeText": "Reset tree", "resetView": "to reset this view", "resulted": "Resulted", @@ -75,12 +76,12 @@ "seeAllResults": "See all results", "showResultsTable": "Show results table", "showTree": "Show tree", - "split": "Split", "startDate": "Start date", "testName": "Test name", "testResults": "test results", "testResults_title": "Test Results", "testResultsData": "Test results data", + "tests": "Tests", "testType": "Test type", "timeline": "Timeline", "tree": "Tree", @@ -99,5 +100,6 @@ "unknown": "Unknown", "usingADifferentTerm": "using a different term", "value": "Value", - "view": "View" + "view": "View", + "viewTimeline": "View timeline" } From 5e7a45822bbbfc5bf13dfbf4ba043b24b75b2454 Mon Sep 17 00:00:00 2001 From: McCarthy <121826239+mccarthyaaron@users.noreply.github.com> Date: Tue, 3 Sep 2024 19:22:31 +0300 Subject: [PATCH 6/6] (fix) O3-3845: Editing a visit does not immediately update the displayed values (#1970) * mutate infinitVisits on form submission * solve error: cannot read proeprties of undefined --- .../src/visit/visit-form/visit-form.component.tsx | 7 ++++++- .../src/visit/visits-widget/visit.resource.tsx | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/esm-patient-chart-app/src/visit/visit-form/visit-form.component.tsx b/packages/esm-patient-chart-app/src/visit/visit-form/visit-form.component.tsx index add7e38d39..a6d9ef3eab 100644 --- a/packages/esm-patient-chart-app/src/visit/visit-form/visit-form.component.tsx +++ b/packages/esm-patient-chart-app/src/visit/visit-form/visit-form.component.tsx @@ -59,7 +59,7 @@ import { useMutateAppointments } from '../hooks/useMutateAppointments'; import { useOfflineVisitType } from '../hooks/useOfflineVisitType'; import { useVisitAttributeTypes } from '../hooks/useVisitAttributeType'; import { useVisitQueueEntry } from '../queue-entry/queue.resource'; -import { useVisits } from '../visits-widget/visit.resource'; +import { useInfiniteVisits, useVisits } from '../visits-widget/visit.resource'; import BaseVisitType from './base-visit-type.component'; import LocationSelector from './location-selector.component'; import VisitAttributeTypeFields from './visit-attribute-type.component'; @@ -97,6 +97,7 @@ const StartVisitForm: React.FC = ({ const { activePatientEnrollment, isLoading } = useActivePatientEnrollment(patientUuid); const { mutate: mutateCurrentVisit } = useVisit(patientUuid); const { mutateVisits } = useVisits(patientUuid); + const { mutateVisits: mutateInfiniteVisits } = useInfiniteVisits(patientUuid); const { mutateAppointments } = useMutateAppointments(); const allVisitTypes = useConditionalVisitTypes(); @@ -482,6 +483,7 @@ const StartVisitForm: React.FC = ({ if (status === 201) { mutateCurrentVisit(); mutateVisits(); + mutateInfiniteVisits(); mutateQueueEntry(); showSnackbar({ kind: 'success', @@ -506,6 +508,7 @@ const StartVisitForm: React.FC = ({ () => { mutateCurrentVisit(); mutateVisits(); + mutateInfiniteVisits(); mutateAppointments(); showSnackbar({ isLowContrast: true, @@ -537,6 +540,7 @@ const StartVisitForm: React.FC = ({ if (!attributesResponses.includes(undefined)) { mutateCurrentVisit(); mutateVisits(); + mutateInfiniteVisits(); closeWorkspace({ ignoreChanges: true }); showSnackbar({ isLowContrast: true, @@ -623,6 +627,7 @@ const StartVisitForm: React.FC = ({ mutateCurrentVisit, mutateQueueEntry, mutateVisits, + mutateInfiniteVisits, patientUuid, priority, queueLocation, diff --git a/packages/esm-patient-chart-app/src/visit/visits-widget/visit.resource.tsx b/packages/esm-patient-chart-app/src/visit/visits-widget/visit.resource.tsx index 38a80b5976..1b636fce50 100644 --- a/packages/esm-patient-chart-app/src/visit/visits-widget/visit.resource.tsx +++ b/packages/esm-patient-chart-app/src/visit/visits-widget/visit.resource.tsx @@ -38,7 +38,7 @@ export function useInfiniteVisits(patientUuid: string) { ); return { - visits: data ? [].concat(data?.flatMap((page) => page.data.results)) : null, + visits: data ? [].concat(data?.flatMap((page) => page?.data?.results)) : null, error, hasMore: data?.length ? !!data[data.length - 1].data?.links?.some((link) => link.rel === 'next') : false, isLoading,