-
Notifications
You must be signed in to change notification settings - Fork 228
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
(feat) O3-3797: Add the Encounter List Table and Tabs to Patient Chart #1987
Open
hadijahkyampeire
wants to merge
19
commits into
main
Choose a base branch
from
O3-3797-encounter-list-table
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,122
−0
Open
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
bf1f6a0
Add encounter list table and tabs
hadijahkyampeire bc05f18
Update translations
hadijahkyampeire ec74019
folder restructuring
hadijahkyampeire 86f1906
Translate nav group title
hadijahkyampeire 9f3f80a
clean up
hadijahkyampeire 19a538a
undo some changes
hadijahkyampeire 38e122c
translate nav group title
hadijahkyampeire 9cf368e
more clean ups
hadijahkyampeire 72531e7
undo some changes
hadijahkyampeire 65bee4d
more clean ups
hadijahkyampeire 9a93dbc
use the restBaseUrl
hadijahkyampeire 680d1d8
code cleanups
hadijahkyampeire 5f87b5c
Use date utils from the framework
hadijahkyampeire b031651
Remove the opening of clinical workspace after form closing
hadijahkyampeire 7d540dd
handle encounter pagination on the backend
hadijahkyampeire 3fd89e6
support translation
hadijahkyampeire 4d63603
Remove unrelated changes
ibacher 4625465
code review
CynthiaKamau 238ea77
code review
CynthiaKamau File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
53 changes: 53 additions & 0 deletions
53
...ges/esm-patient-chart-app/src/encounter-list/components/encounter-list-tabs.component.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import React from 'react'; | ||
import { useConfig, usePatient, useVisit } from '@openmrs/esm-framework'; | ||
import { useTranslation } from 'react-i18next'; | ||
import { Tabs, Tab, TabList, TabPanels, TabPanel } from '@carbon/react'; | ||
import { EncounterList } from './encounter-list.component'; | ||
import { getMenuItemTabsConfiguration } from '../utils/encounter-list-config-builder'; | ||
import styles from './encounter-list-tabs.scss'; | ||
import { filter } from '../utils/helpers'; | ||
|
||
interface EncounterListTabsComponentProps { | ||
patientUuid: string; | ||
} | ||
|
||
const EncounterListTabsComponent: React.FC<EncounterListTabsComponentProps> = ({ patientUuid }) => { | ||
const config = useConfig(); | ||
const { tabDefinitions = [] } = config; | ||
const { t } = useTranslation(); | ||
const tabsConfig = getMenuItemTabsConfiguration(tabDefinitions); | ||
const patient = usePatient(patientUuid); | ||
const { currentVisit } = useVisit(patientUuid); | ||
|
||
return ( | ||
<div className={styles.tabContainer}> | ||
<Tabs> | ||
<TabList contained> | ||
{tabsConfig.map((tab) => ( | ||
<Tab key={tab.name}>{t(tab.name)}</Tab> | ||
))} | ||
</TabList> | ||
<TabPanels> | ||
{tabsConfig.map((tab) => ( | ||
<TabPanel key={tab.name}> | ||
<EncounterList | ||
filter={tab.hasFilter ? (encounter) => filter(encounter, tab.formList[0].uuid) : null} | ||
patientUuid={patientUuid} | ||
formList={tab.formList} | ||
columns={tab.columns} | ||
encounterType={tab.encounterType} | ||
launchOptions={tab.launchOptions} | ||
headerTitle={tab.headerTitle} | ||
description={tab.description} | ||
currentVisit={currentVisit} | ||
deathStatus={patient?.patient?.deceasedBoolean} | ||
/> | ||
</TabPanel> | ||
))} | ||
</TabPanels> | ||
</Tabs> | ||
</div> | ||
); | ||
}; | ||
|
||
export default EncounterListTabsComponent; |
7 changes: 7 additions & 0 deletions
7
packages/esm-patient-chart-app/src/encounter-list/components/encounter-list-tabs.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
.tabContainer div[role='tabpanel'] { | ||
padding: 0 !important; | ||
} | ||
|
||
.tabContainer li button { | ||
width: 100% !important; | ||
} |
296 changes: 296 additions & 0 deletions
296
packages/esm-patient-chart-app/src/encounter-list/components/encounter-list.component.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,296 @@ | ||
import React, { useCallback, useMemo, useState } from 'react'; | ||
import { navigate, showModal, showSnackbar, Visit } from '@openmrs/esm-framework'; | ||
import { EmptyState } from '@openmrs/esm-patient-common-lib'; | ||
import { useTranslation } from 'react-i18next'; | ||
import { EncounterListDataTable } from './table.component'; | ||
import { Button, Link, OverflowMenu, OverflowMenuItem, DataTableSkeleton, Pagination } from '@carbon/react'; | ||
import { Add } from '@carbon/react/icons'; | ||
import { launchEncounterForm } from '../utils/helpers'; | ||
import { deleteEncounter } from '../encounter-list.resource'; | ||
import { useEncounterRows, useFormsJson } from '../hooks'; | ||
|
||
import styles from './encounter-list.scss'; | ||
import { type TableRow, type Encounter, type Mode } from '../types'; | ||
|
||
export interface EncounterListColumn { | ||
key: string; | ||
header: string; | ||
getValue: (encounter: Encounter) => string; | ||
link?: any; | ||
} | ||
|
||
export interface EncounterListProps { | ||
patientUuid: string; | ||
encounterType: string; | ||
columns: Array<any>; | ||
headerTitle: string; | ||
description: string; | ||
formList?: Array<{ | ||
name?: string; | ||
uuid: string; | ||
excludedIntents?: Array<string>; | ||
fixedIntent?: string; | ||
isDefault?: boolean; | ||
}>; | ||
launchOptions: { | ||
hideFormLauncher?: boolean; | ||
displayText?: string; | ||
workspaceWindowSize?: 'minimized' | 'maximized'; | ||
}; | ||
filter?: (encounter: Encounter) => boolean; | ||
afterFormSaveAction?: () => void; | ||
deathStatus?: boolean; | ||
currentVisit: Visit; | ||
} | ||
|
||
export const EncounterList: React.FC<EncounterListProps> = ({ | ||
patientUuid, | ||
encounterType, | ||
columns, | ||
headerTitle, | ||
description, | ||
formList, | ||
filter, | ||
launchOptions, | ||
afterFormSaveAction, | ||
currentVisit, | ||
deathStatus, | ||
}) => { | ||
const { t } = useTranslation(); | ||
|
||
const [currentPage, setCurrentPage] = useState(1); | ||
const [pageSize, setPageSize] = useState(10); | ||
|
||
const { formsJson, isLoading: isLoadingFormsJson, error: errorFormJson } = useFormsJson(formList[0].uuid); | ||
const { encounters, total, isLoading, onFormSave, mutate } = useEncounterRows( | ||
patientUuid, | ||
encounterType, | ||
filter, | ||
afterFormSaveAction, | ||
pageSize, | ||
currentPage, | ||
); | ||
|
||
const { workspaceWindowSize, displayText, hideFormLauncher } = launchOptions; | ||
|
||
const defaultActions = useMemo( | ||
() => [ | ||
{ | ||
label: t('viewEncounter', 'View'), | ||
form: { | ||
name: formsJson?.name, | ||
}, | ||
mode: 'view', | ||
intent: '*', | ||
}, | ||
{ | ||
label: t('editEncounter', 'Edit'), | ||
form: { | ||
name: formsJson?.name, | ||
}, | ||
mode: 'edit', | ||
intent: '*', | ||
}, | ||
{ | ||
label: t('deleteEncounter', 'Delete'), | ||
form: { | ||
name: formsJson?.name, | ||
}, | ||
mode: 'delete', | ||
intent: '*', | ||
}, | ||
], | ||
[formsJson, t], | ||
); | ||
|
||
const createLaunchFormAction = useCallback( | ||
(encounter: Encounter, mode: Mode) => () => { | ||
launchEncounterForm(formsJson, currentVisit, mode, onFormSave, encounter.uuid, null, patientUuid); | ||
}, | ||
[formsJson, onFormSave, patientUuid, currentVisit], | ||
); | ||
|
||
const handleDeleteEncounter = useCallback( | ||
(encounterUuid, encounterTypeName) => { | ||
const close = showModal('delete-encounter-modal', { | ||
close: () => close(), | ||
encounterTypeName: encounterTypeName || '', | ||
onConfirmation: () => { | ||
const abortController = new AbortController(); | ||
deleteEncounter(encounterUuid, abortController) | ||
.then(() => { | ||
onFormSave(); | ||
mutate(); | ||
showSnackbar({ | ||
isLowContrast: true, | ||
title: t('encounterDeleted', 'Encounter deleted'), | ||
subtitle: `Encounter ${t('successfullyDeleted', 'successfully deleted')}`, | ||
kind: 'success', | ||
}); | ||
}) | ||
.catch(() => { | ||
showSnackbar({ | ||
isLowContrast: false, | ||
title: t('error', 'Error'), | ||
subtitle: `Encounter ${t('failedDeleting', "couldn't be deleted")}`, | ||
kind: 'error', | ||
}); | ||
}) | ||
.finally(() => { | ||
close(); | ||
}); | ||
}, | ||
}); | ||
}, | ||
[onFormSave, t, mutate], | ||
); | ||
|
||
const tableRows = useMemo(() => { | ||
return encounters.map((encounter: Encounter) => { | ||
const tableRow: TableRow = { id: encounter.uuid, actions: null }; | ||
|
||
encounter['launchFormActions'] = { | ||
editEncounter: createLaunchFormAction(encounter, 'edit'), | ||
viewEncounter: createLaunchFormAction(encounter, 'view'), | ||
}; | ||
|
||
columns.forEach((column) => { | ||
let val = column?.getValue(encounter); | ||
if (column.link) { | ||
val = ( | ||
<Link | ||
onClick={(e) => { | ||
e.preventDefault(); | ||
if (column.link.handleNavigate) { | ||
column.link.handleNavigate(encounter); | ||
} else { | ||
column.link?.getUrl && navigate({ to: column.link.getUrl() }); | ||
} | ||
}} | ||
> | ||
{val} | ||
</Link> | ||
); | ||
} | ||
tableRow[column.key] = val; | ||
}); | ||
|
||
const actions = | ||
Array.isArray(tableRow.actions) && tableRow.actions.length > 0 ? tableRow.actions : defaultActions; | ||
|
||
tableRow['actions'] = ( | ||
<OverflowMenu flipped className={styles.flippedOverflowMenu} data-testid="actions-id"> | ||
{actions.map((actionItem, index) => { | ||
const form = formsJson && actionItem?.form?.name ? formsJson.name === actionItem.form.name : null; | ||
|
||
return ( | ||
form && ( | ||
<OverflowMenuItem | ||
key={index} | ||
index={index} | ||
itemText={t(actionItem.label)} | ||
onClick={(e) => { | ||
e.preventDefault(); | ||
actionItem.mode === 'delete' | ||
? handleDeleteEncounter(encounter.uuid, encounter.encounterType.name) | ||
: launchEncounterForm( | ||
formsJson, | ||
currentVisit, | ||
actionItem.mode === 'enter' ? 'add' : actionItem.mode, | ||
onFormSave, | ||
encounter.uuid, | ||
actionItem.intent, | ||
patientUuid, | ||
); | ||
}} | ||
/> | ||
) | ||
); | ||
})} | ||
</OverflowMenu> | ||
); | ||
|
||
return tableRow; | ||
}); | ||
}, [ | ||
encounters, | ||
createLaunchFormAction, | ||
columns, | ||
defaultActions, | ||
formsJson, | ||
t, | ||
handleDeleteEncounter, | ||
onFormSave, | ||
patientUuid, | ||
currentVisit, | ||
]); | ||
|
||
const headers = useMemo(() => { | ||
if (columns) { | ||
return columns.map((column) => { | ||
return { key: column.key, header: t(column.header) }; | ||
}); | ||
} | ||
return []; | ||
}, [columns, t]); | ||
|
||
const formLauncher = useMemo(() => { | ||
if (formsJson && !formsJson['availableIntents']?.length) { | ||
return ( | ||
<Button | ||
kind="ghost" | ||
renderIcon={Add} | ||
iconDescription="Add" | ||
onClick={(e) => { | ||
e.preventDefault(); | ||
launchEncounterForm(formsJson, currentVisit, 'add', onFormSave, '', '*', patientUuid); | ||
}} | ||
> | ||
{t(displayText)} | ||
</Button> | ||
); | ||
} | ||
return null; | ||
}, [formsJson, displayText, onFormSave, patientUuid, t, currentVisit]); | ||
|
||
if (isLoading === true || isLoadingFormsJson === true) { | ||
return <DataTableSkeleton rowCount={10} />; | ||
} | ||
|
||
return ( | ||
<> | ||
{tableRows?.length > 0 || encounters.length > 0 ? ( | ||
<> | ||
<div className={styles.widgetContainer}> | ||
<div className={styles.widgetHeaderContainer}> | ||
<h4 className={`${styles.productiveHeading03} ${styles.text02}`}>{t(headerTitle)}</h4> | ||
{/* @ts-ignore */} | ||
{!(hideFormLauncher ?? deathStatus) && <div className={styles.toggleButtons}>{formLauncher}</div>} | ||
</div> | ||
<EncounterListDataTable tableHeaders={headers} tableRows={tableRows} /> | ||
<Pagination | ||
page={currentPage} | ||
pageSizes={[10, 20, 30, 40, 50]} | ||
onChange={({ page, pageSize }) => { | ||
setCurrentPage(page); | ||
setPageSize(pageSize); | ||
}} | ||
pageSize={pageSize} | ||
totalItems={total} | ||
/> | ||
</div> | ||
</> | ||
) : ( | ||
<EmptyState | ||
displayText={description} | ||
headerTitle={t(headerTitle)} | ||
launchForm={ | ||
hideFormLauncher || deathStatus | ||
? null | ||
: () => launchEncounterForm(formsJson, currentVisit, 'add', onFormSave, '', '*', patientUuid) | ||
} | ||
/> | ||
)} | ||
</> | ||
); | ||
}; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be explicitly typed.