Skip to content

Commit

Permalink
(feat) Visual enhancements to the forms widget (#929)
Browse files Browse the repository at this point in the history
* (feat) Visual enhancements to the forms widget

* Review feedback
  • Loading branch information
denniskigen authored Jan 11, 2023
1 parent cdbfbd7 commit 9223fd1
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 120 deletions.
222 changes: 125 additions & 97 deletions packages/esm-patient-forms-app/src/forms/form-view.component.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,35 @@
import React, { useState, useEffect, useMemo } from 'react';
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import isEmpty from 'lodash-es/isEmpty';
import first from 'lodash-es/first';
import debounce from 'lodash-es/debounce';
import {
DataTable,
DataTableHeader,
DataTableRow,
Layer,
Search,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableHeader,
TableRow,
TableToolbar,
TableToolbarContent,
TableToolbarSearch,
Tile,
} from '@carbon/react';
import { Edit } from '@carbon/react/icons';
import { EmptyDataIllustration, PatientChartPagination, useVisitOrOfflineVisit } from '@openmrs/esm-patient-common-lib';
import { isDesktop, formatDatetime, useConfig, useLayoutType, usePagination } from '@openmrs/esm-framework';
import { formatDatetime, useConfig, usePagination } from '@openmrs/esm-framework';
import { ConfigObject } from '../config-schema';
import { CompletedFormInfo } from '../types';
import { launchFormEntryOrHtmlForms } from '../form-entry-interop';
import styles from './form-view.scss';

type FormsCategory = 'All' | 'Completed' | 'Recommended';

interface FormViewProps {
category?: FormsCategory;
forms: Array<CompletedFormInfo>;
patientUuid: string;
patient: fhir.Patient;
Expand All @@ -35,28 +38,25 @@ interface FormViewProps {
urlLabel: string;
}

const FormView: React.FC<FormViewProps> = ({ forms, patientUuid, patient, pageSize, pageUrl, urlLabel }) => {
interface FilterProps {
rowIds: Array<string>;
headers: Array<Record<string, string>>;
cellsById: any;
inputValue: string;
getCellId: (row, key) => string;
}

const FormView: React.FC<FormViewProps> = ({ category, forms, patientUuid, patient, pageSize, pageUrl, urlLabel }) => {
const { t } = useTranslation();
const config = useConfig() as ConfigObject;
const htmlFormEntryForms = config.htmlFormEntryForms;
const layout = useLayoutType();
const { currentVisit } = useVisitOrOfflineVisit(patientUuid);
const [searchTerm, setSearchTerm] = useState<string>(null);
const [allFormInfos, setAllFormInfos] = useState<Array<CompletedFormInfo>>(forms);

const { results, goTo, currentPage } = usePagination(
allFormInfos.sort((a, b) => (b.lastCompleted?.getTime() ?? 0) - (a.lastCompleted?.getTime() ?? 0)),
forms?.sort((a, b) => (b.lastCompleted?.getTime() ?? 0) - (a.lastCompleted?.getTime() ?? 0)),
pageSize,
);

const handleSearch = useMemo(() => debounce((searchTerm) => setSearchTerm(searchTerm), 300), []);

useEffect(() => {
const entriesToDisplay = isEmpty(searchTerm)
? forms
: forms.filter((formInfo) => formInfo.form.name.toLowerCase().search(searchTerm?.toLowerCase()) !== -1);
setAllFormInfos(entriesToDisplay);
}, [searchTerm, forms]);

const tableHeaders: Array<DataTableHeader> = useMemo(
() => [
{
Expand All @@ -82,54 +82,82 @@ const FormView: React.FC<FormViewProps> = ({ forms, patientUuid, patient, pageSi
[results],
);

const handleFilter = ({ rowIds, headers, cellsById, inputValue, getCellId }: FilterProps): Array<string> => {
return rowIds.filter((rowId) =>
headers.some(({ key }) => {
const cellId = getCellId(rowId, key);
const filterableValue = cellsById[cellId].value;
const filterTerm = inputValue.toLowerCase();

return ('' + filterableValue).toLowerCase().includes(filterTerm);
}),
);
};

if (!forms?.length) {
return (
<Layer>
<Tile className={styles.tile}>
<EmptyDataIllustration />
<p className={styles.content}>
{t('noMatchingFormsAvailable', 'There are no {formCategory} forms to display', {
formCategory: category?.toLowerCase(),
})}
</p>
<p className={styles.helper}>{t('formSearchHint', 'Try using an alternative name or keyword')}</p>
</Tile>
</Layer>
);
}

return (
<div className={styles.formContainer}>
<Search
id="searchInput"
labelText=""
className={styles.formSearchInput}
placeholder={t('searchForForm', 'Search for a form')}
onChange={(e) => handleSearch(e.target.value)}
/>
<>
{searchTerm?.length > 0 && allFormInfos?.length > 0 && (
<p className={styles.formResultsLabel}>
{allFormInfos.length} {t('matchesFound', 'match(es) found')}
</p>
)}
{allFormInfos?.length > 0 && (
<>
<DataTable
size={isDesktop(layout) ? 'sm' : 'lg'}
rows={tableRows}
headers={tableHeaders}
isSortable
useZebraStyles
>
{({ rows, headers, getHeaderProps, getTableProps }) => (
<TableContainer className={styles.tableContainer}>
<Table {...getTableProps()}>
<TableHead>
<TableRow>
{headers.map((header) => (
<TableHeader
className={`${styles.productiveHeading01} ${styles.text02}`}
{...getHeaderProps({
header,
isSortable: header.isSortable,
})}
>
{header.header?.content ?? header.header}
</TableHeader>
))}
</TableRow>
</TableHead>
<TableBody>
{rows.map((row, index) => {
return (
<TableRow key={row.id}>
<TableCell>{row.cells[0].value ?? t('never', 'Never')}</TableCell>
<TableCell className={styles.tableCell}>
{forms?.length > 0 && (
<>
<DataTable
filterRows={handleFilter}
headers={tableHeaders}
rows={tableRows}
size="sm"
isSortable
useZebraStyles
>
{({ rows, headers, getHeaderProps, getTableProps, onInputChange }) => (
<TableContainer className={styles.tableContainer}>
<TableToolbar className={styles.tableToolbar}>
<TableToolbarContent>
<TableToolbarSearch
className={styles.searchInput}
expanded
light
onChange={onInputChange}
placeholder={t('searchThisList', 'Search this list')}
/>
</TableToolbarContent>
</TableToolbar>
<Table {...getTableProps()} className={styles.table}>
<TableHead>
<TableRow>
{headers.map((header) => (
<TableHeader
className={`${styles.heading} ${styles.text02}`}
{...getHeaderProps({
header,
isSortable: header.isSortable,
})}
>
{header.header}
</TableHeader>
))}
</TableRow>
</TableHead>
<TableBody>
{rows.map((row, index) => {
return (
<TableRow key={row.id}>
<TableCell>{row.cells[0].value ?? t('never', 'Never')}</TableCell>
<TableCell>
<div className={styles.tableCell}>
<label
onClick={() =>
launchFormEntryOrHtmlForms(
Expand Down Expand Up @@ -162,38 +190,38 @@ const FormView: React.FC<FormViewProps> = ({ forms, patientUuid, patient, pageSi
}
/>
)}
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
)}
</DataTable>
<PatientChartPagination
pageNumber={currentPage}
totalItems={allFormInfos.length}
currentItems={results.length}
pageSize={pageSize}
onPageNumberChange={({ page }) => goTo(page)}
dashboardLinkUrl={pageUrl}
dashboardLinkLabel={urlLabel}
/>
</>
)}
{isEmpty(allFormInfos) ? (
<Layer>
<Tile className={styles.tile}>
<EmptyDataIllustration />
<p className={styles.content}>{t('noFormsAvailable', 'There are no matching forms to display')}</p>
<p className={styles.helper}>
{t('formSearchHint', 'Try searching for the form using an alternative name or keyword')}
</p>
</Tile>
</Layer>
) : null}
</>
</div>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
{rows.length === 0 ? (
<div className={styles.tileContainer}>
<Tile className={styles.tile}>
<div className={styles.tileContent}>
<p className={styles.content}>
{t('noMatchingFormsToDisplay', 'No matching forms to display')}
</p>
</div>
</Tile>
</div>
) : null}
</TableContainer>
)}
</DataTable>
<PatientChartPagination
pageNumber={currentPage}
totalItems={forms?.length}
currentItems={results.length}
pageSize={pageSize}
onPageNumberChange={({ page }) => goTo(page)}
dashboardLinkUrl={pageUrl}
dashboardLinkLabel={urlLabel}
/>
</>
)}
</div>
);
};
Expand Down
36 changes: 34 additions & 2 deletions packages/esm-patient-forms-app/src/forms/form-view.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
@import '~@openmrs/esm-styleguide/src/vars';
@import '../root.scss';

.productiveHeading01 {
.heading {
@include type.type-style("heading-compact-01");
}

.text02 {
color: $text-02;
}

.bodyShort01 {
@include type.type-style("body-compact-01");
}
Expand All @@ -31,7 +35,29 @@
}

.formResultsLabel {
@include type.type-style('body-compact-01');
margin: spacing.$spacing-05;
color: $text-02;
}

.table {
margin-top: 3rem;
}

.tileContainer {
background-color: $ui-02;
margin: 3rem;
}

.tile {
margin: auto;
width: fit-content;
}

.tileContent {
display: flex;
flex-direction: column;
align-items: center;
}

// Desktop viewport
Expand Down Expand Up @@ -68,7 +94,6 @@
height: spacing.$spacing-09;
align-items: center;


& > label {
cursor: pointer;
}
Expand All @@ -95,3 +120,10 @@
color: $color-gray-70;
margin: 0.5rem 0;
}

.searchInput {
input:focus {
outline: none;
border: spacing.$spacing-01 solid var(--omrs-color-brand-orange);
}
}
Loading

0 comments on commit 9223fd1

Please sign in to comment.