Skip to content

Commit

Permalink
Feat: Added bill manager (#277)
Browse files Browse the repository at this point in the history
* feat: billable service for patients improvement

* refactor: file structure and created four workspace components

* chore: translations

* feat: added cancel and delete dialogs

* fix: billable services side nav issue

* fix: workspaces, fix forms and also upgrade the ui

* feat: Created edit form

* Apply suggestions from code review

Co-authored-by: Donald Kibet <[email protected]>

* refactor: changes from code review

* fix (tests) failing waive bill form tests

---------

Co-authored-by: Donald Kibet <[email protected]>
  • Loading branch information
amosmachora and donaldkibet authored Jul 29, 2024
1 parent 33583c7 commit 072d98b
Show file tree
Hide file tree
Showing 23 changed files with 1,970 additions and 1,017 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"zod": "^3.21.4"
},
"devDependencies": {
"@openmrs/esm-framework": "^5.6.1-pre.1895",
"@openmrs/esm-framework": "next",
"@openmrs/esm-patient-common-lib": "next",
"@playwright/test": "^1.30.0",
"@swc/cli": "^0.1.57",
Expand Down Expand Up @@ -73,7 +73,7 @@
"jest-environment-jsdom": "^28.1.2",
"lerna": "^4.0.0",
"lodash": "^4.17.21",
"openmrs": "^5.6.1-pre.1895",
"openmrs": "next",
"pinst": "^3.0.0",
"prettier": "^2.2.1",
"pretty-quick": "^2.0.2",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import {
StructuredListHead,
StructuredListRow,
Expand All @@ -7,35 +7,40 @@ import {
StructuredListWrapper,
Layer,
Checkbox,
OverflowMenu,
OverflowMenuItem,
} from '@carbon/react';
import { useTranslation } from 'react-i18next';
import { convertToCurrency, extractString } from '../../helpers';
import { MappedBill, LineItem } from '../../types';
import styles from './bill-waiver.scss';
import BillWaiverForm from './bill-waiver-form.component';
import { LineItem, MappedBill } from '../../types';
import styles from './bill-manager.scss';
import { launchWorkspace, showModal } from '@openmrs/esm-framework';

const PatientBillsSelections: React.FC<{ bills: MappedBill; setPatientUuid: (patientUuid) => void }> = ({
bills,
setPatientUuid,
}) => {
const BillLineItems: React.FC<{ bill: MappedBill }> = ({ bill }) => {
const { t } = useTranslation();
const [selectedBills, setSelectedBills] = React.useState<Array<LineItem>>([]);

const checkBoxLabel = (lineItem) => {
return `${lineItem.item === '' ? lineItem.billableService : lineItem.item} ${convertToCurrency(lineItem.price)}`;
const handleOpenEditLineItemWorkspace = (lineItem: LineItem) => {
launchWorkspace('edit-bill-form', {
workspaceTitle: t('editBillForm', 'Edit Bill Form'),
lineItem,
});
};

const handleOnCheckBoxChange = (event, { checked, id }) => {
const selectedLineItem = bills.lineItems.find((lineItem) => lineItem.uuid === id);
if (checked) {
setSelectedBills([...selectedBills, selectedLineItem]);
} else {
setSelectedBills(selectedBills.filter((lineItem) => lineItem.uuid !== id));
}
const handleOpenCancelLineItemModal = () => {
const dispose = showModal('cancel-bill-modal', {
onClose: () => dispose(),
});
};

const handleOpenDeleteLineItemModal = () => {
const dispose = showModal('delete-bill-modal', {
onClose: () => dispose(),
});
};

return (
<Layer>
<StructuredListWrapper className={styles.billListContainer} isCondensed selection={true}>
<StructuredListWrapper className={styles.billListContainer} selection={true} isCondensed>
<StructuredListHead>
<StructuredListRow head>
<StructuredListCell head>{t('billItem', 'Bill item')}</StructuredListCell>
Expand All @@ -46,7 +51,7 @@ const PatientBillsSelections: React.FC<{ bills: MappedBill; setPatientUuid: (pat
</StructuredListRow>
</StructuredListHead>
<StructuredListBody>
{bills?.lineItems.map((lineItem) => (
{bill?.lineItems.map((lineItem) => (
<StructuredListRow>
<StructuredListCell>
{lineItem.item === '' ? extractString(lineItem.billableService) : extractString(lineItem.item)}
Expand All @@ -55,20 +60,18 @@ const PatientBillsSelections: React.FC<{ bills: MappedBill; setPatientUuid: (pat
<StructuredListCell>{convertToCurrency(lineItem.price)}</StructuredListCell>
<StructuredListCell>{convertToCurrency(lineItem.price * lineItem.quantity)}</StructuredListCell>
<StructuredListCell>
<Checkbox
hideLabel
onChange={(event, { checked, id }) => handleOnCheckBoxChange(event, { checked, id })}
labelText={checkBoxLabel(lineItem)}
id={lineItem.uuid}
/>
<OverflowMenu aria-label="overflow-menu" align="bottom">
<OverflowMenuItem itemText="Edit Item" onClick={() => handleOpenEditLineItemWorkspace(lineItem)} />
<OverflowMenuItem itemText="Cancel Item" onClick={handleOpenCancelLineItemModal} />
<OverflowMenuItem itemText="Delete Item" onClick={handleOpenDeleteLineItemModal} />
</OverflowMenu>
</StructuredListCell>
</StructuredListRow>
))}
</StructuredListBody>
</StructuredListWrapper>
<BillWaiverForm bill={bills} lineItems={selectedBills} setPatientUuid={setPatientUuid} />
</Layer>
);
};

export default PatientBillsSelections;
export default BillLineItems;
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React from 'react';
import { ExtensionSlot, UserHasAccess, WorkspaceContainer } from '@openmrs/esm-framework';
import PatientBills from './patient-bills.component';
import styles from './bill-manager.scss';
import billTableStyles from '../../bills-table/bills-table.scss';
import { useBills } from '../../billing.resource';
import { DataTableSkeleton, Layer, Tile } from '@carbon/react';
import { EmptyDataIllustration } from '@openmrs/esm-patient-common-lib';
import { useTranslation } from 'react-i18next';

type BillManagerProps = {};

const headers = [
{ header: 'Date', key: 'date' },
{ header: 'Billable Service', key: 'billableService' },
{ header: 'Total Amount', key: 'totalAmount' },
];

const BillManager: React.FC<BillManagerProps> = () => {
const [patientUuid, setPatientUuid] = React.useState<string>(undefined);
const { t } = useTranslation();

const { bills, isLoading } = useBills(patientUuid);
const filteredBills = bills.filter((bill) => bill.status !== 'PAID' && patientUuid === bill.patientUuid) ?? [];

return (
<UserHasAccess privilege="coreapps.systemAdministration">
<div className={styles.billManagerContainer}>
<ExtensionSlot
name="patient-search-bar-slot"
state={{
selectPatientAction: (patientUuid: string) => setPatientUuid(patientUuid),
buttonProps: {
kind: 'primary',
},
}}
/>
{!patientUuid ? (
<div style={{ marginTop: '0.625rem' }}>
<Layer className={billTableStyles.emptyStateContainer}>
<Tile className={billTableStyles.tile}>
<div className={billTableStyles.illo}>
<EmptyDataIllustration />
</div>
<p className={billTableStyles.content}>
{t('notSearchedState', 'Please search for a patient in the input above')}
</p>
</Tile>
</Layer>
</div>
) : isLoading ? (
<DataTableSkeleton
headers={headers}
aria-label="patient bills table"
showToolbar={false}
showHeader={false}
rowCount={3}
zebra
columnCount={3}
className={styles.dataTableSkeleton}
/>
) : (
bills && <PatientBills bills={filteredBills} />
)}
</div>
<WorkspaceContainer overlay contextKey="bill-manager" />
</UserHasAccess>
);
};

export default BillManager;
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
@use '@carbon/layout';

.billWaiverContainer {
.billManagerContainer {
margin: layout.$layout-01;
row-gap: layout.$layout-01;
}

.billListContainer {
background-color: white;
}

.dataTableSkeleton {
margin-top: 0.625rem;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import { ModalHeader, ModalBody, ModalFooter, Button } from '@carbon/react';
import styles from './cancel-bill.scss';
import { useTranslation } from 'react-i18next';

export const CancelBillModal: React.FC<{
onClose: () => void;
}> = ({ onClose }) => {
const { t } = useTranslation();
return (
<React.Fragment>
<ModalHeader onClose={onClose} className={styles.modalHeaderLabel} closeModal={onClose}>
{t('cancelBill', 'Cancel Bill')}
</ModalHeader>
<ModalBody className={styles.modalHeaderHeading}>
{t('cancelBillDescription', 'Are you sure you want to cancel this bill? Proceed cautiously.')}
</ModalBody>
<ModalFooter>
<Button kind="secondary" onClick={onClose}>
{t('cancel', 'Cancel')}
</Button>
<Button kind="danger">{t('continue', 'Continue')}</Button>
</ModalFooter>
</React.Fragment>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@use '@carbon/type';

.modalHeaderLabel {
@include type.type-style('label-02');
}

.modalHeaderHeading {
@include type.type-style('heading-compact-02');
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';
import { ModalHeader, ModalBody, ModalFooter, Button } from '@carbon/react';
import cancelBillStyles from './cancel-bill.scss';

export const DeleteBillModal: React.FC<{
onClose: () => void;
}> = ({ onClose }) => {
return (
<React.Fragment>
<ModalHeader onClose={onClose} className={cancelBillStyles.modalHeaderLabel} closeModal={onClose}>
Delete bill
</ModalHeader>
<ModalBody className={cancelBillStyles.modalHeaderHeading}>
Are you sure you want to delete this bill? Proceed cautiously.
</ModalBody>
<ModalFooter>
<Button kind="secondary" onClick={onClose}>
Cancel
</Button>
<Button kind="danger">Continue</Button>
</ModalFooter>
</React.Fragment>
);
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import {
Layer,
DataTable,
Expand All @@ -13,27 +13,24 @@ import {
TableCell,
TableExpandedRow,
Tile,
Button,
} from '@carbon/react';
import { convertToCurrency, extractString } from '../../helpers';
import { useTranslation } from 'react-i18next';
import { EmptyDataIllustration } from '@openmrs/esm-patient-common-lib';
import PatientBillsSelections from './bill-selection.component';
import { EmptyDataIllustration, EmptyState } from '@openmrs/esm-patient-common-lib';
import { MappedBill } from '../../types';
import styles from '../../bills-table/bills-table.scss';
import BillLineItems from './bill-line-items.component';
import { Scalpel } from '@carbon/react/icons';
import { launchWorkspace } from '@openmrs/esm-framework';

type PatientBillsProps = {
patientUuid: string;
bills: Array<MappedBill>;
setPatientUuid: (patientUuid: string) => void;
};

const PatientBills: React.FC<PatientBillsProps> = ({ patientUuid, bills, setPatientUuid }) => {
const PatientBills: React.FC<PatientBillsProps> = ({ bills }) => {
const { t } = useTranslation();

if (!patientUuid) {
return;
}

const tableHeaders = [
{ header: 'Date', key: 'date' },
{ header: 'Billable Service', key: 'billableService' },
Expand All @@ -47,20 +44,21 @@ const PatientBills: React.FC<PatientBillsProps> = ({ patientUuid, bills, setPati
totalAmount: convertToCurrency(bill.totalAmount),
}));

if (bills.length === 0 && patientUuid !== '') {
const handleOpenWaiveBillWorkspace = (bill: MappedBill) => {
launchWorkspace('waive-bill-form', {
workspaceTitle: 'Waive Bill Form',
bill,
});
};

if (bills.length === 0) {
return (
<>
<div style={{ marginTop: '0.625rem' }}>
<Layer className={styles.emptyStateContainer}>
<Tile className={styles.tile}>
<div className={styles.illo}>
<EmptyDataIllustration />
</div>
<p className={styles.content}>{t('noBillDisplay', 'There are no bills to display for this patient')}</p>
</Tile>
</Layer>
</div>
</>
<div style={{ marginTop: '1rem' }}>
<EmptyState
displayText={t('noBillDisplay', 'There are no bills to display for this patient')}
headerTitle="No bills"
/>
</div>
);
}

Expand All @@ -69,6 +67,7 @@ const PatientBills: React.FC<PatientBillsProps> = ({ patientUuid, bills, setPati
<DataTable
rows={tableRows}
headers={tableHeaders}
compact
size="sm"
useZebraStyles
render={({
Expand All @@ -88,7 +87,7 @@ const PatientBills: React.FC<PatientBillsProps> = ({ patientUuid, bills, setPati
<Table {...getTableProps()} aria-label="sample table">
<TableHead>
<TableRow>
<TableExpandHeader enableToggle={true} {...getExpandHeaderProps()} />
<TableExpandHeader {...getExpandHeaderProps()} />
{headers.map((header, i) => (
<TableHeader
key={i}
Expand All @@ -98,6 +97,7 @@ const PatientBills: React.FC<PatientBillsProps> = ({ patientUuid, bills, setPati
{header.header}
</TableHeader>
))}
<TableHeader>Action</TableHeader>
</TableRow>
</TableHead>
<TableBody>
Expand All @@ -110,16 +110,20 @@ const PatientBills: React.FC<PatientBillsProps> = ({ patientUuid, bills, setPati
{row.cells.map((cell) => (
<TableCell key={cell.id}>{cell.value}</TableCell>
))}
<TableCell>
<Scalpel
onClick={() => handleOpenWaiveBillWorkspace(bills[index])}
className={styles.scalpel}
/>
</TableCell>
</TableExpandRow>
<TableExpandedRow
colSpan={headers.length + 1}
className="demo-expanded-td"
colSpan={headers.length + 2}
className={styles.expendableRow}
{...getExpandedRowProps({
row,
})}>
<div>
<PatientBillsSelections bills={bills[index]} setPatientUuid={setPatientUuid} />
</div>
<BillLineItems bill={bills[index]} />
</TableExpandedRow>
</React.Fragment>
))}
Expand Down
Loading

0 comments on commit 072d98b

Please sign in to comment.