-
Notifications
You must be signed in to change notification settings - Fork 77
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
(feat) create billing status component
- Loading branch information
1 parent
b0fe32a
commit 766089e
Showing
7 changed files
with
518 additions
and
52 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
import React, { useMemo } from 'react'; | ||
import { useTranslation } from 'react-i18next'; | ||
import { | ||
CheckmarkFilledIcon, | ||
CloseFilledIcon, | ||
ErrorState, | ||
formatDate, | ||
useLayoutType, | ||
usePagination, | ||
} from '@openmrs/esm-framework'; | ||
import { CardHeader, PatientChartPagination } from '@openmrs/esm-patient-common-lib'; | ||
import styles from './billing-status-summary.scss'; | ||
import { | ||
DataTable, | ||
DataTableSkeleton, | ||
InlineLoading, | ||
Table, | ||
TableBody, | ||
TableCell, | ||
TableContainer, | ||
TableExpandedRow, | ||
TableExpandHeader, | ||
TableExpandRow, | ||
TableHead, | ||
TableHeader, | ||
TableRow, | ||
Tile, | ||
} from '@carbon/react'; | ||
import { useBillingStatus } from '../resources/billing-status.resource'; | ||
|
||
interface PatientBillingStatusSummaryProps { | ||
patient: fhir.Patient; | ||
} | ||
|
||
const PatientBillingStatusSummary: React.FC<PatientBillingStatusSummaryProps> = ({ patient }) => { | ||
const defaultPageSize = 10; | ||
const { t } = useTranslation(); | ||
const headerTitle = t('billingStatus', 'Billing Status'); | ||
const layout = useLayoutType(); | ||
const isTablet = layout === 'tablet'; | ||
const isDesktop = layout === 'small-desktop' || layout === 'large-desktop'; | ||
|
||
const { groupedLines, isLoading, isValidating, error } = useBillingStatus(patient.id); | ||
|
||
const tableRows = useMemo(() => { | ||
if (!groupedLines) return []; | ||
return Object.entries(groupedLines).map(([visitId, group]) => { | ||
return { | ||
id: visitId, | ||
visitDate: `${formatDate(new Date(group.visit.startDate))} - ${formatDate(new Date(group.visit.endDate))}`, | ||
status: group.approved, | ||
lines: group.lines, | ||
}; | ||
}); | ||
}, [groupedLines]); | ||
|
||
const { results: paginatedRows, goTo, currentPage } = usePagination(tableRows, defaultPageSize); | ||
|
||
const headers = [ | ||
{ key: 'visitDate', header: 'Visit Date' }, | ||
{ key: 'status', header: 'Status' }, | ||
]; | ||
|
||
if (isLoading) return <DataTableSkeleton role="progressbar" compact={isDesktop} zebra />; | ||
if (error) return <ErrorState error={error} headerTitle={headerTitle} />; | ||
|
||
return ( | ||
<div className={styles.widgetCard}> | ||
<CardHeader title={headerTitle}> | ||
<span>{isValidating ? <InlineLoading /> : null}</span> | ||
</CardHeader> | ||
<DataTable | ||
aria-label={t('orderBillingStatuses', 'Order Billing Statuses')} | ||
data-floating-menu-container | ||
overflowMenuOnHover={!isTablet} | ||
isSortable | ||
rows={paginatedRows} | ||
headers={headers} | ||
useZebraStyles | ||
> | ||
{({ | ||
rows, | ||
headers, | ||
getHeaderProps, | ||
getRowProps, | ||
getTableProps, | ||
getTableContainerProps, | ||
getExpandHeaderProps, | ||
}) => ( | ||
<> | ||
<TableContainer {...getTableContainerProps()}> | ||
<Table {...getTableProps()} className={styles.table}> | ||
<TableHead> | ||
<TableRow> | ||
<TableExpandHeader enableToggle {...getExpandHeaderProps()} /> | ||
{headers.map((header: { header: string }) => ( | ||
<TableHeader {...getHeaderProps({ header })}>{header.header}</TableHeader> | ||
))} | ||
</TableRow> | ||
</TableHead> | ||
<TableBody> | ||
{rows.map((row: { id: React.Key; cells: { value: any }[]; isExpanded: any }) => ( | ||
<React.Fragment key={row.id}> | ||
<TableExpandRow className={styles.row} {...getRowProps({ row })}> | ||
<TableCell>{row.cells[0].value}</TableCell> | ||
<TableCell> | ||
{row.cells[1].value ? ( | ||
<CheckmarkFilledIcon className={styles.approvedIcon} /> | ||
) : ( | ||
<CloseFilledIcon className={styles.warningIcon} /> | ||
)} | ||
</TableCell> | ||
</TableExpandRow> | ||
{row.isExpanded && paginatedRows.find((r) => r.id === row.id)?.lines?.length > 0 ? ( | ||
<TableExpandedRow colSpan={headers.length + 2}> | ||
<div className={styles.expandedContent}> | ||
{paginatedRows | ||
.find((r) => r.id === row.id) | ||
?.lines?.map((line) => ( | ||
<div key={line.id} className={styles.expandedTile}> | ||
<div className={styles.statusIcon}> | ||
{line.approved ? ( | ||
<CheckmarkFilledIcon className={styles.approvedIcon} /> | ||
) : ( | ||
<CloseFilledIcon className={styles.warningIcon} /> | ||
)} | ||
</div> | ||
<div className={styles.nameSection}>{line.displayName}</div> | ||
<div className={styles.documentSection}>{line.document}</div> | ||
</div> | ||
))} | ||
</div> | ||
</TableExpandedRow> | ||
) : ( | ||
<TableExpandedRow className={styles.hiddenRow} colSpan={headers.length + 2} /> | ||
)} | ||
</React.Fragment> | ||
))} | ||
</TableBody> | ||
</Table> | ||
</TableContainer> | ||
{rows.length === 0 ? ( | ||
<div className={styles.tileContainer}> | ||
<Tile className={styles.emptyStateTile}> | ||
<div className={styles.tileContent}> | ||
<p className={styles.content}>{t('noMatchingOrdersToDisplay', 'No billing status to display')}</p> | ||
</div> | ||
</Tile> | ||
</div> | ||
) : null} | ||
</> | ||
)} | ||
</DataTable> | ||
<div className={styles.paginationContainer}> | ||
<PatientChartPagination | ||
pageNumber={currentPage} | ||
totalItems={tableRows.length} | ||
currentItems={paginatedRows.length} | ||
pageSize={defaultPageSize} | ||
onPageNumberChange={({ page }) => goTo(page)} | ||
/> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default PatientBillingStatusSummary; |
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,122 @@ | ||
@use '@carbon/type'; | ||
@use '@carbon/colors'; | ||
@use '@carbon/layout'; | ||
@use '@openmrs/esm-styleguide/src/vars' as vars; | ||
|
||
.widgetCard { | ||
border: 1px solid colors.$gray-20; | ||
} | ||
|
||
.row { | ||
span { | ||
margin: auto; | ||
} | ||
|
||
p { | ||
padding: layout.$spacing-02 0; | ||
} | ||
} | ||
|
||
.tile { | ||
text-align: center; | ||
border-bottom: 1px solid vars.$ui-03; | ||
|
||
.message { | ||
@include type.type-style('heading-compact-01'); | ||
color: vars.$text-02; | ||
} | ||
} | ||
|
||
.table { | ||
td { | ||
&::after { | ||
display: none; | ||
} | ||
} | ||
} | ||
|
||
.hiddenRow { | ||
display: none; | ||
} | ||
|
||
.layer { | ||
height: 100%; | ||
} | ||
|
||
.unknownOrderTypeText { | ||
@include type.type-style('body-compact-01'); | ||
} | ||
|
||
.paginationContainer { | ||
margin-top: auto; | ||
|
||
> div { | ||
border-top: none !important; | ||
} | ||
} | ||
|
||
.toolBarContent { | ||
background-color: colors.$gray-10; | ||
} | ||
|
||
.content { | ||
@include type.type-style('heading-compact-02'); | ||
color: colors.$gray-70; | ||
margin-bottom: layout.$spacing-03; | ||
} | ||
|
||
.tileContainer { | ||
background-color: colors.$white-0; | ||
border-top: 1px solid colors.$gray-70; | ||
padding: layout.$spacing-11 0; | ||
} | ||
|
||
.emptyStateTile { | ||
margin: auto; | ||
width: fit-content; | ||
} | ||
|
||
.tileContent { | ||
display: flex; | ||
flex-direction: column; | ||
align-items: center; | ||
} | ||
|
||
.helperText { | ||
@include type.type-style('helper-text-02'); | ||
} | ||
|
||
.expandedContent { | ||
padding: layout.$spacing-05 0; | ||
} | ||
|
||
.expandedTile { | ||
display: flex; | ||
flex-flow: row nowrap; | ||
align-items: center; | ||
padding: layout.$spacing-02; | ||
border-bottom: 1px solid colors.$gray-30; | ||
} | ||
|
||
.statusIcon { | ||
flex: 0 0 auto; | ||
margin-right: layout.$spacing-05; | ||
} | ||
|
||
.nameSection { | ||
flex: 1 1 auto; | ||
} | ||
|
||
.documentSection { | ||
flex: 0 0 auto; | ||
margin-right: layout.$spacing-05; | ||
} | ||
|
||
//.approveIcon { | ||
// padding: layout.$spacing-02; | ||
// border-radius: 50%; | ||
// fill: $ui-03; | ||
// background-color: $color-blue-60-2; | ||
// margin-left: layout.$spacing-03; | ||
// margin-right: layout.$spacing-02; | ||
//} |
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,80 @@ | ||
// import React from 'react'; | ||
// import { screen } from '@testing-library/react'; | ||
// import { openmrsFetch } from '@openmrs/esm-framework'; | ||
// import { mockFhirAllergyIntoleranceResponse } from '__mocks__'; | ||
// import { mockPatient, renderWithSwr, waitForLoadingToFinish } from 'tools'; | ||
// import AllergiesDetailedSummary from './allergies-detailed-summary.component'; | ||
// | ||
// const mockOpenmrsFetch = openmrsFetch as jest.Mock; | ||
// mockOpenmrsFetch.mockImplementation(jest.fn()); | ||
// | ||
// describe('AllergiesDetailedSummary', () => { | ||
// it('renders an empty state view if allergy data is unavailable', async () => { | ||
// mockOpenmrsFetch.mockReturnValueOnce({ data: { entry: [] } }); | ||
// renderWithSwr(<AllergiesDetailedSummary patient={mockPatient} />); | ||
// await waitForLoadingToFinish(); | ||
// | ||
// expect(screen.queryByRole('table')).not.toBeInTheDocument(); | ||
// expect(screen.getByRole('heading', { name: /allergies/i })).toBeInTheDocument(); | ||
// expect(screen.getByText(/There are no allergy intolerances to display for this patient/i)).toBeInTheDocument(); | ||
// expect(screen.getByText(/Record allergy intolerances/i)).toBeInTheDocument(); | ||
// }); | ||
// | ||
// it('renders an error state view if there was a problem fetching allergies data', async () => { | ||
// const error = { | ||
// message: 'You are not logged in', | ||
// response: { | ||
// status: 401, | ||
// statusText: 'Unauthorized', | ||
// }, | ||
// }; | ||
// mockOpenmrsFetch.mockRejectedValueOnce(error); | ||
// renderWithSwr(<AllergiesDetailedSummary patient={mockPatient} />); | ||
// await waitForLoadingToFinish(); | ||
// | ||
// expect(screen.queryByRole('table')).not.toBeInTheDocument(); | ||
// expect(screen.getByRole('heading', { name: /allergies/i })).toBeInTheDocument(); | ||
// expect(screen.getByText(/Error 401: Unauthorized/i)).toBeInTheDocument(); | ||
// expect( | ||
// screen.getByText( | ||
// /Sorry, there was a problem displaying this information. You can try to reload this page, or contact the site administrator and quote the error code above/i, | ||
// ), | ||
// ).toBeInTheDocument(); | ||
// }); | ||
// | ||
// it("renders a detailed summary of the patient's allergic reactions and their manifestations", async () => { | ||
// mockOpenmrsFetch.mockReturnValueOnce({ data: mockFhirAllergyIntoleranceResponse }); | ||
// renderWithSwr(<AllergiesDetailedSummary patient={mockPatient} />); | ||
// await waitForLoadingToFinish(); | ||
// | ||
// expect(screen.getByRole('heading', { name: /allergies/i })).toBeInTheDocument(); | ||
// | ||
// const expectedColumnHeaders = [/allergen/i, /severity/i, /reaction/i, /onset date and comments/i]; | ||
// const expectedAllergies = [ | ||
// /ACE inhibitors moderate Anaphylaxis/i, | ||
// /Fish mild Anaphylaxis, Angioedema, Fever, Hives Some Comments/i, | ||
// /Penicillins severe Mental status change, Angioedema, Cough, Diarrhea, Musculoskeletal pain Patient allergies have been noted down/i, | ||
// /Morphine severe Mental status change Comments/i, | ||
// /Aspirin severe Mental status change Comments/i, | ||
// ]; | ||
// | ||
// expectedColumnHeaders.forEach((header) => | ||
// expect(screen.getByRole('columnheader', { name: new RegExp(header) })).toBeInTheDocument(), | ||
// ); | ||
// | ||
// expectedAllergies.forEach((allergy) => | ||
// expect(screen.getByRole('row', { name: new RegExp(allergy) })).toBeInTheDocument(), | ||
// ); | ||
// }); | ||
// | ||
// it("renders non-coded allergen name and non-coded allergic reaction name in the detailed summary of the patient's allergies", async () => { | ||
// mockOpenmrsFetch.mockReturnValueOnce({ data: mockFhirAllergyIntoleranceResponse }); | ||
// renderWithSwr(<AllergiesDetailedSummary patient={mockPatient} />); | ||
// await waitForLoadingToFinish(); | ||
// | ||
// expect(screen.getByRole('heading', { name: /allergies/i })).toBeInTheDocument(); | ||
// | ||
// const expectedNonCodedAllergy = /non-coded allergen severe non-coded allergic reaction non coded allergic note/i; | ||
// expect(screen.getByRole('row', { name: new RegExp(expectedNonCodedAllergy) })).toBeInTheDocument(); | ||
// }); | ||
// }); |
Oops, something went wrong.