-
Notifications
You must be signed in to change notification settings - Fork 47
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
KHP3-6234: Enhanced Case Management Functionality: CRUD Operations, T…
…able Integration, and Conditional Dropdown Behavior (#285) * (feat) added a dashboard link for case management encounter * (feat) added header and combox for selecting forms * (feat) added enhancement on case management and improved on CRUD on special clinics * (refactor)translation
- Loading branch information
1 parent
8c2c8f0
commit a133306
Showing
11 changed files
with
626 additions
and
96 deletions.
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
87 changes: 87 additions & 0 deletions
87
...s/esm-patient-clinical-view-app/src/case-management/encounters/case-encounter-header.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,87 @@ | ||
@use '@carbon/layout'; | ||
@use '@carbon/colors'; | ||
@use '@carbon/type'; | ||
@import '~@openmrs/esm-styleguide/src/vars'; | ||
|
||
.headerContainer { | ||
display: flex; | ||
justify-content: space-between; | ||
background-color: $ui-02; | ||
height: layout.$spacing-11; | ||
align-items: center; | ||
padding: 0 layout.$spacing-05; | ||
} | ||
.headerTitle { | ||
color: $ui-05; | ||
position: relative; | ||
} | ||
|
||
.headerTitle::after { | ||
content: ''; | ||
display: block; | ||
width: 2rem; | ||
padding-top: 1.188rem; | ||
border-bottom: 0.375rem solid var(--brand-03); | ||
position: absolute; | ||
left: 0; | ||
bottom: -0.5rem; | ||
top: 0.5rem; | ||
} | ||
|
||
.actionBtn { | ||
display: flex; | ||
column-gap: 0.5rem; | ||
justify-content: center; | ||
} | ||
.comboBox input[type='text'] { | ||
background-color: white; | ||
cursor: default; | ||
::placeholder { | ||
color: black; | ||
} | ||
} | ||
|
||
.desktopHeading, | ||
.tabletHeading { | ||
text-align: left; | ||
text-transform: capitalize; | ||
|
||
h4 { | ||
@include type.type-style('heading-compact-02'); | ||
color: colors.$gray-70; | ||
|
||
&:after { | ||
content: ''; | ||
display: block; | ||
width: 2rem; | ||
padding-top: 3px; | ||
border-bottom: 0.375rem solid; | ||
@include brand-03(border-bottom-color); | ||
} | ||
} | ||
} | ||
|
||
.widgetContainer { | ||
background-color: colors.$white-0; | ||
border: 1px solid colors.$gray-20; | ||
margin-bottom: 1rem; | ||
} | ||
|
||
.widgetContainer :global(.cds--data-table) thead th button span { | ||
height: unset !important; | ||
} | ||
|
||
.tile { | ||
text-align: center; | ||
} | ||
|
||
.emptyStateContainer { | ||
margin: 2rem 0; | ||
} | ||
|
||
.content { | ||
@include type.type-style('heading-compact-01'); | ||
color: colors.$gray-70; | ||
margin-top: layout.$layout-05; | ||
margin-bottom: layout.$spacing-03; | ||
} |
231 changes: 231 additions & 0 deletions
231
...nt-clinical-view-app/src/case-management/encounters/case-encounter-overview.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,231 @@ | ||
import React, { useState } from 'react'; | ||
import { useTranslation } from 'react-i18next'; | ||
import { useConfig, formatDate, showModal, showSnackbar, isDesktop, useLayoutType } from '@openmrs/esm-framework'; | ||
import { EmptyDataIllustration, launchPatientWorkspace, PatientChartPagination } from '@openmrs/esm-patient-common-lib'; | ||
import { ComboBox, Dropdown, DataTableSkeleton, Layer, Tile } from '@carbon/react'; | ||
import { KeyedMutator } from 'swr'; | ||
import styles from './case-encounter-header.scss'; | ||
import GenericTable from '../../specialized-clinics/generic-nav-links/generic-table.component'; | ||
import { useInfiniteVisits, deleteEncounter } from './case-encounter-table.resource'; | ||
import { ConfigObject } from '../../config-schema'; | ||
|
||
interface CaseEncounterProps { | ||
mutate: KeyedMutator<any>; | ||
patientUuid: string; | ||
onFilterChange: (formUuid: string) => void; | ||
} | ||
|
||
interface CaseEncounterOverviewComponentProps { | ||
patientUuid: string; | ||
} | ||
|
||
const CaseEncounterHeader = ({ patientUuid, mutate, onFilterChange }: CaseEncounterProps) => { | ||
const { t } = useTranslation(); | ||
const title = t('caseEncounter', 'Case management encounters'); | ||
const { caseManagementForms } = useConfig<ConfigObject>(); | ||
|
||
const handleOpenOrEditClinicalEncounterForm = (formUuid: string, encounterUUID = '') => { | ||
launchPatientWorkspace('patient-form-entry-workspace', { | ||
workspaceTitle: 'Clinical Encounter', | ||
mutateForm: mutate, | ||
formInfo: { | ||
encounterUuid: encounterUUID, | ||
formUuid, | ||
patientUuid, | ||
visitTypeUuid: '', | ||
visitUuid: '', | ||
}, | ||
}); | ||
}; | ||
|
||
const items = caseManagementForms.map((form) => ({ | ||
id: form.id, | ||
text: form.title, | ||
formUuid: form.formUuid, | ||
filterUuid: form.formUuid, | ||
})); | ||
|
||
const handleComboBoxChange = ({ selectedItem }) => { | ||
if (selectedItem) { | ||
handleOpenOrEditClinicalEncounterForm(selectedItem.formUuid); | ||
} | ||
}; | ||
|
||
const handleEncounterTypeChange = ({ selectedItem }) => { | ||
onFilterChange(selectedItem.filterUuid); | ||
}; | ||
|
||
return ( | ||
<div className={styles.headerContainer}> | ||
<span className={styles.headerTitle}>{title}</span> | ||
<div className={styles.actionBtn}> | ||
<Dropdown | ||
id="serviceFilter" | ||
initialSelectedItem={{ text: t('all', 'All'), filterUuid: '' }} | ||
label="" | ||
titleText={t('filterByForm', 'Filter by form') + ':'} | ||
type="inline" | ||
items={[{ text: t('all', 'All'), filterUuid: '' }, ...items]} | ||
itemToString={(item) => (item ? item.text : '')} | ||
onChange={handleEncounterTypeChange} | ||
size="lg" | ||
/> | ||
<ComboBox | ||
onChange={handleComboBoxChange} | ||
id="select-form" | ||
items={items} | ||
itemToString={(item) => (item ? item.text : '')} | ||
placeholder="Select forms" | ||
className={styles.comboBox} | ||
/> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
const CaseEncounterOverviewComponent = ({ patientUuid }: CaseEncounterOverviewComponentProps) => { | ||
const { visits, isLoading, error, hasMore, isValidating, mutateVisits, setSize, size } = | ||
useInfiniteVisits(patientUuid); | ||
const { t } = useTranslation(); | ||
const { caseManagementForms } = useConfig<ConfigObject>(); | ||
const [filterFormUuid, setFilterFormUuid] = useState(''); | ||
const [currentPage, setCurrentPage] = useState(1); | ||
const [pageSize, setPageSize] = useState(10); | ||
const layout = useLayoutType(); | ||
|
||
const formUuids = caseManagementForms.map((form) => form.formUuid); | ||
|
||
const filteredEncounters = | ||
visits | ||
?.flatMap((visit) => visit.encounters) | ||
.filter((encounter) => | ||
filterFormUuid ? encounter.form?.uuid === filterFormUuid : formUuids.includes(encounter.form?.uuid), | ||
) || []; | ||
|
||
const visitTypeMap = visits?.reduce((acc, visit) => { | ||
visit.encounters.forEach((encounter) => { | ||
acc[encounter.uuid] = visit.visitType?.display ?? '--'; | ||
}); | ||
return acc; | ||
}, {} as Record<string, string>); | ||
|
||
const paginatedEncounters = filteredEncounters.slice((currentPage - 1) * pageSize, currentPage * pageSize); | ||
|
||
const genericTableHeader = [ | ||
{ header: 'Date', key: 'encounterDatetime' }, | ||
{ header: 'Visit Type', key: 'visitType' }, | ||
{ header: 'Encounter type', key: 'encounterType' }, | ||
{ header: 'Form name', key: 'formName' }, | ||
]; | ||
|
||
function formatProviderName(display) { | ||
if (!display) { | ||
return '--'; | ||
} | ||
return display.split(':')[0].trim(); | ||
} | ||
|
||
const rows = paginatedEncounters.map((encounter) => ({ | ||
id: `${encounter.uuid}`, | ||
encounterDatetime: formatDate(new Date(encounter.encounterDatetime)), | ||
visitType: visitTypeMap[encounter.uuid] ?? '--', | ||
encounterType: encounter.encounterType?.display ?? '--', | ||
formName: encounter.form?.display ?? '--', | ||
})); | ||
|
||
const handleWorkspaceEditForm = (encounterUuid: string) => { | ||
const encounter = paginatedEncounters.find((enc) => enc.uuid === encounterUuid); | ||
const workspaceTitle = encounter.form?.display ?? ''; | ||
const encounterTypeUuid = encounter.encounterType?.uuid ?? ''; | ||
const formUuid = encounter.form?.uuid ?? ''; | ||
|
||
launchPatientWorkspace('patient-form-entry-workspace', { | ||
workspaceTitle, | ||
mutateForm: mutateVisits, | ||
formInfo: { | ||
encounterUuid, | ||
formUuid, | ||
additionalProps: { | ||
encounterTypeUuid, | ||
}, | ||
}, | ||
}); | ||
}; | ||
|
||
const handleDeleteEncounter = React.useCallback( | ||
(encounterUuid: string, encounterTypeName?: string) => { | ||
const close = showModal('delete-encounter-modal', { | ||
close: () => close(), | ||
encounterTypeName: encounterTypeName || '', | ||
onConfirmation: () => { | ||
const abortController = new AbortController(); | ||
deleteEncounter(encounterUuid, abortController) | ||
.then(() => { | ||
mutateVisits?.(); | ||
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', | ||
}); | ||
}); | ||
close(); | ||
}, | ||
}); | ||
}, | ||
[t, mutateVisits], | ||
); | ||
|
||
const handlePageChange = ({ page }) => { | ||
setCurrentPage(page); | ||
}; | ||
|
||
if (isLoading) { | ||
return <DataTableSkeleton rowCount={10} />; | ||
} | ||
|
||
return ( | ||
<> | ||
<CaseEncounterHeader patientUuid={patientUuid} mutate={mutateVisits} onFilterChange={setFilterFormUuid} /> | ||
{filteredEncounters.length === 0 ? ( | ||
<Layer> | ||
<Tile className={styles.tile}> | ||
<EmptyDataIllustration /> | ||
<p className={styles.content}> | ||
{t('noEncounterToDisplay', 'There are no encounters to display for this patient.')} | ||
</p> | ||
</Tile> | ||
</Layer> | ||
) : ( | ||
<> | ||
<GenericTable | ||
encounters={paginatedEncounters} | ||
onEdit={handleWorkspaceEditForm} | ||
onDelete={handleDeleteEncounter} | ||
headers={genericTableHeader} | ||
rows={rows} | ||
/> | ||
{filteredEncounters.length > pageSize && ( | ||
<PatientChartPagination | ||
currentItems={paginatedEncounters.length} | ||
onPageNumberChange={handlePageChange} | ||
pageNumber={currentPage} | ||
pageSize={pageSize} | ||
totalItems={filteredEncounters.length} | ||
/> | ||
)} | ||
</> | ||
)} | ||
</> | ||
); | ||
}; | ||
|
||
export default CaseEncounterOverviewComponent; |
Oops, something went wrong.