Skip to content

Commit

Permalink
KHP3-6234: Enhanced Case Management Functionality: CRUD Operations, T…
Browse files Browse the repository at this point in the history
…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
its-kios09 authored Jul 25, 2024
1 parent 8c2c8f0 commit a133306
Show file tree
Hide file tree
Showing 11 changed files with 626 additions and 96 deletions.
1 change: 0 additions & 1 deletion packages/esm-billing-app/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@
"selectExemptionCategory": "Select exemption category",
"selectitemstobeclaimed": "Select items that are to be included in the claims",
"selectPaymentMethod": "Select payment method",
"selectPaymentMethodPlaceholder": "Select payment method",
"sellingAmount": "Enter selling price",
"sellingPrice": "Selling Price",
"sendClaim": "Claim sent successfully",
Expand Down
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;
}
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;
Loading

0 comments on commit a133306

Please sign in to comment.