Skip to content

Commit

Permalink
(feat) create billing status component
Browse files Browse the repository at this point in the history
  • Loading branch information
usamaidrsk committed Oct 3, 2024
1 parent b0fe32a commit 766089e
Show file tree
Hide file tree
Showing 7 changed files with 518 additions and 52 deletions.
167 changes: 167 additions & 0 deletions src/components/billing-status-summary.component.tsx
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;
122 changes: 122 additions & 0 deletions src/components/billing-status-summary.scss
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;
//}
80 changes: 80 additions & 0 deletions src/components/billing-status-summary.test.tsx
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();
// });
// });
Loading

0 comments on commit 766089e

Please sign in to comment.