Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Visit Summary added to Active visits widget #28

Merged
merged 10 commits into from
Aug 3, 2021
Next Next commit
Added Expandable rows and copied visit summary from the chart app
  • Loading branch information
vasharma05 committed Jul 27, 2021
commit fc864361ca7305c94144ba4681e94ad68db93224
Original file line number Diff line number Diff line change
@@ -9,6 +9,9 @@ import DataTable, {
TableCell,
TableToolbar,
TableToolbarContent,
TableExpandRow,
TableExpandedRow,
TableExpandHeader,
} from 'carbon-components-react/es/components/DataTable';
import DataTableSkeleton from 'carbon-components-react/es/components/DataTableSkeleton';
import Pagination from 'carbon-components-react/es/components/Pagination';
@@ -70,13 +73,17 @@ const ActiveVisitsTable = (props) => {
const layout = useLayoutType();
const desktopView = layout === 'desktop';
const config = useConfig();
const [currentPage, setCurrentPage] = useState(1);
const [currentPageSize, setPageSize] = useState(config?.activeVisits?.pageSize ?? 10);
const pageSizes = config?.activeVisits?.pageSizes ?? [10, 20, 50];
const [loading, setLoading] = useState(true);
const [activeVisits, setActiveVisits] = useState<ActiveVisitRow[]>([]);
const [searchString, setSearchString] = useState('');

const searchResults = useMemo(() => {
if (currentPage != 1) {
setCurrentPage(1);
}
if (searchString && searchString.trim() !== '') {
const search = searchString.toLowerCase();
return activeVisits.filter((activeVisitRow) =>
@@ -91,7 +98,11 @@ const ActiveVisitsTable = (props) => {
return activeVisits;
}
}, [searchString, activeVisits]);
const { goTo, currentPage, results } = usePagination(searchResults, currentPageSize);
const { goTo, results } = usePagination(searchResults, currentPageSize);

useEffect(() => {
goTo(currentPage);
}, [currentPage]);

useEffect(() => {
const activeVisits = fetchActiveVisits().subscribe((data) => {
@@ -119,7 +130,7 @@ const ActiveVisitsTable = (props) => {
<h4 className={styles.productiveHeading02}>{t('activeVisits', 'Active Visits')}</h4>
</div>
<DataTable rows={results} headers={headerData} isSortable>
{({ rows, headers, getHeaderProps, getTableProps, getBatchActionProps }) => (
{({ rows, headers, getHeaderProps, getTableProps, getBatchActionProps, getRowProps }) => (
<TableContainer title="" className={styles.tableContainer}>
<TableToolbar>
<TableToolbarContent>
@@ -130,29 +141,40 @@ const ActiveVisitsTable = (props) => {
/>
</TableToolbarContent>
</TableToolbar>
<Table {...getTableProps()} useZebraStyles>
<Table className={styles.customTable} {...getTableProps()} size={desktopView ? 'short' : 'normal'}>
<TableHead>
<TableRow style={{ height: desktopView ? '2rem' : '3rem' }}>
<TableRow>
<TableExpandHeader />
{headers.map((header) => (
<TableHeader {...getHeaderProps({ header })}>{header.header}</TableHeader>
))}
</TableRow>
</TableHead>
<TableBody>
{rows.map((row, ind) => (
<TableRow key={row.id} style={{ height: desktopView ? '2rem' : '3rem' }}>
{row.cells.map((cell) => (
<TableCell key={cell.id}>
{cell.info.header === 'name' ? (
<ConfigurableLink to={`\${openmrsSpaBase}/patient/${results[ind]?.patientUuid}/chart/`}>
{cell.value}
</ConfigurableLink>
) : (
cell.value
)}
</TableCell>
))}
</TableRow>
<React.Fragment key={row.id}>
<TableExpandRow {...getRowProps({ row })}>
{row.cells.map((cell) => (
<TableCell key={cell.id}>
{cell.info.header === 'name' ? (
<ConfigurableLink to={`\${openmrsSpaBase}/patient/${results[ind]?.patientUuid}/chart/`}>
{cell.value}
</ConfigurableLink>
) : (
cell.value
)}
</TableCell>
))}
</TableExpandRow>
{row.isExpanded && (
<TableExpandedRow
className={styles.expandedRow}
style={{ paddingLeft: desktopView ? '3rem' : '4rem' }}
colSpan={headers.length + 2}>
<p>Aux squad rules</p>
</TableExpandedRow>
)}
</React.Fragment>
))}
</TableBody>
</Table>
@@ -176,7 +198,7 @@ const ActiveVisitsTable = (props) => {
setPageSize(pageSize);
}
if (page !== currentPage) {
goTo(page);
setCurrentPage(page);
}
}}
/>
Original file line number Diff line number Diff line change
@@ -41,3 +41,15 @@
display: flex;
align-items: center;
}

.customTable tr[data-parent-row]:nth-child(odd) td {
background-color: #fff;
}

.customTable tbody tr[data-parent-row]:nth-child(even) td {
background-color: #f4f4f4;
}

.expandedRow > td > div {
max-height: max-content !important;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import React, { useEffect, useState } from 'react';
import DataTable, {
TableContainer,
Table,
TableHead,
TableExpandHeader,
TableRow,
TableHeader,
TableBody,
TableExpandRow,
TableCell,
TableExpandedRow,
} from 'carbon-components-react/es/components/DataTable';
import EncounterObservations from './encounter-observations.component';
import styles from '../visit-detail-overview.scss';
import { Observation } from '../visit.resource';
import { useTranslation } from 'react-i18next';
import { useLayoutType } from '@openmrs/esm-framework';

interface EncounterListProps {
encounters: Array<{
id: any;
time: any;
encounterType: string;
provider: string;
obs: Array<Observation>;
}>;
visitUuid: string;
}

const EncounterListDataTable: React.FC<EncounterListProps> = ({ encounters, visitUuid }) => {
const { t } = useTranslation();
const layout = useLayoutType();
const isDesktop = layout === 'desktop';
const [headerWidth, setHeaderWidth] = useState(0);

const headerData = [
{
id: 1,
header: 'Time',
key: 'time',
},
{
id: 2,
header: 'Encounter Type',
key: 'encounterType',
},
{
id: 3,
header: 'Provider',
key: 'provider',
},
];

useEffect(() => {
setHeaderWidth(document.getElementById(`header_${visitUuid}_0`)?.clientWidth);
const handler = () => setHeaderWidth(document.getElementById(`header_${visitUuid}_0`)?.clientWidth);
window.addEventListener('resize', handler);
return () => window.removeEventListener('resize', handler);
}, []);

return encounters.length !== 0 ? (
<DataTable rows={encounters} headers={headerData}>
{({ rows, headers, getHeaderProps, getRowProps, getTableProps }) => {
return (
<TableContainer>
<Table className={styles.customTable} {...getTableProps()} size={!isDesktop ? 'normal' : 'short'}>
<TableHead>
<TableRow>
<TableExpandHeader />
{headers.map((header, ind) => (
<TableHeader id={`header_${visitUuid}_${ind}`} {...getHeaderProps({ header })}>
{header.header}
</TableHeader>
))}
</TableRow>
</TableHead>
<TableBody>
{rows.map((row, ind) => (
<React.Fragment key={row.id}>
<TableExpandRow {...getRowProps({ row })}>
{row.cells.map((cell, ind) => (
<TableCell key={cell.id}>{cell.value}</TableCell>
))}
</TableExpandRow>
{row.isExpanded && (
<TableExpandedRow
className={styles.expandedRow}
style={{ paddingLeft: isDesktop ? '3rem' : '4rem' }}
colSpan={headers.length + 2}>
<div style={{ marginLeft: headerWidth }}>
<EncounterObservations observations={encounters[ind].obs} />
</div>
</TableExpandedRow>
)}
</React.Fragment>
))}
</TableBody>
</Table>
</TableContainer>
);
}}
</DataTable>
) : (
<div className={styles.encounterEmptyState}>
<h4 className={styles.productiveHeading02}>{t('noEncountersFound', 'No encounters found')}</h4>
<p className={`${styles.bodyLong01} ${styles.text02}`}>
{t('thereIsNoInformationToDisplayHere', 'There is no information to display here')}
</p>
</div>
);
};

export default EncounterListDataTable;
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React, { useState, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import SkeletonText from 'carbon-components-react/es/components/SkeletonText';
import { Observation } from '../visit.resource';
import styles from '../visit-detail-overview.scss';

interface EncounterObservationsProps {
observations: Array<Observation>;
}

const EncounterObservations: React.FC<EncounterObservationsProps> = ({ observations }) => {
const { t } = useTranslation();

const observationsList = useMemo(() => {
return (
observations &&
observations.map((obs: Observation) => {
const [question, answer] = obs.display.split(':');
return { question, answer };
})
);
}, [observations]);

return observationsList ? (
observationsList.length > 0 ? (
<div className={styles.observation}>
{observationsList.map((obs, ind) => (
<React.Fragment key={ind}>
<span className={styles.caption01}>{obs.question}: </span>
<span className={`${styles.bodyShort02} ${styles.text01}`}>{obs.answer}</span>
</React.Fragment>
))}
</div>
) : (
<p className={styles.caption01}>{t('noObservationsFound', 'No observations found')}</p>
)
) : (
<SkeletonText />
);
};

export default EncounterObservations;
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React from 'react';
import capitalize from 'lodash-es/capitalize';
import dayjs from 'dayjs';
import { useTranslation } from 'react-i18next';
import { OrderItem, getDosage } from '../visit.resource';
import styles from '../visit-detail-overview.scss';

function formatTime(dateTime) {
return dayjs(dateTime).format('hh:mm');
}

interface MedicationSummaryProps {
medications: Array<OrderItem>;
}

const MedicationSummary: React.FC<MedicationSummaryProps> = ({ medications }) => {
const { t } = useTranslation();

return (
<React.Fragment>
{medications.length > 0 ? (
medications.map(
(medication: OrderItem, ind) =>
medication.order?.dose &&
medication.order?.orderType?.display === 'Drug Order' && (
<React.Fragment key={ind}>
<p className={`${styles.bodyLong01} ${styles.medicationBlock}`}>
<strong>{capitalize(medication.order.drug?.name)}</strong> &mdash;{' '}
{medication.order.doseUnits?.display} &mdash; {medication.order.route?.display}
<br />
<span className={styles.label01}>{t('dose', 'Dose').toUpperCase()}</span>{' '}
<strong>{getDosage(medication.order.drug?.strength, medication.order.dose).toLowerCase()}</strong>{' '}
&mdash; {medication.order.frequency?.display} &mdash;{' '}
{!medication.order.duration
? t('orderIndefiniteDuration', 'Indefinite duration')
: t('orderDurationAndUnit', 'for {duration} {durationUnit}', {
duration: medication.order.duration,
durationUnit: medication.order.durationUnits?.display,
})}
<br />
<span className={styles.label01}>{t('refills', 'Refills').toUpperCase()}</span>{' '}
{medication.order.numRefills}
</p>
<p className={styles.caption01} style={{ color: '#525252' }}>
{formatTime(medication.order.dateActivated)} &middot;{' '}
{medication.provider && medication.provider.name} &middot;{' '}
{medication.provider && medication.provider.role}
</p>
</React.Fragment>
),
)
) : (
<p className={`${styles.bodyLong01} ${styles.text02}`}>{t('noMedicationsFound', 'No medications found')}</p>
)}
</React.Fragment>
);
};

export default MedicationSummary;
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import styles from '../visit-detail-overview.scss';
import { Note } from '../visit.resource';
import { useTranslation } from 'react-i18next';

interface NotesSummaryProps {
notes: Array<Note>;
}

const NotesSummary: React.FC<NotesSummaryProps> = ({ notes }) => {
const { t } = useTranslation();

return (
<React.Fragment>
{notes.length > 0 ? (
notes.map((note: Note, ind) => (
<React.Fragment key={ind}>
<p className={`${styles.medicationBlock} ${styles.bodyLong01}`}>{note.note}</p>
<p className={styles.caption01} style={{ color: '#525252' }}>
{note.time} &middot; {note.provider.name} &middot; {note.provider.role}
</p>
</React.Fragment>
))
) : (
<p className={`${styles.bodyLong01} ${styles.text02}`}>{t('noNotesFound', 'No notes found')}</p>
)}
</React.Fragment>
);
};

export default NotesSummary;
Loading
Oops, something went wrong.