Files
ohif-viewer/platform/ui/src/components/studyList/StudyList.js
T

409 lines
11 KiB
JavaScript
Raw Normal View History

2019-08-13 15:36:46 -04:00
import './StudyList.styl';
2019-10-28 14:47:57 -04:00
import React from 'react';
import classNames from 'classnames';
import TableSearchFilter from './TableSearchFilter.js';
2019-08-13 15:36:46 -04:00
import PropTypes from 'prop-types';
import { StudyListLoadingText } from './StudyListLoadingText.js';
import { useTranslation } from 'react-i18next';
2019-08-13 15:36:46 -04:00
const getContentFromUseMediaValue = (
displaySize,
contentArrayMap,
defaultContent
) => {
const content =
displaySize in contentArrayMap
? contentArrayMap[displaySize]
: defaultContent;
return content;
};
2019-10-28 14:47:57 -04:00
/**
*
*
* @param {*} props
* @returns
*/
function StudyList(props) {
const {
isLoading,
hasError,
studies,
sort,
onSort: handleSort,
filterValues,
onFilterChange: handleFilterChange,
onSelectItem: handleSelectItem,
studyListDateFilterNumDays,
displaySize,
2019-10-28 14:47:57 -04:00
} = props;
const { t, ready: translationsAreReady } = useTranslation('StudyList');
2019-10-28 14:47:57 -04:00
const largeTableMeta = [
{
displayText: t('PatientName'),
fieldName: 'patientName',
inputType: 'text',
size: 330,
},
2019-10-28 14:47:57 -04:00
{
displayText: t('MRN'),
fieldName: 'patientId',
inputType: 'text',
size: 378,
},
2019-10-28 14:47:57 -04:00
{
displayText: t('AccessionNumber'),
fieldName: 'accessionNumber',
inputType: 'text',
size: 180,
},
2019-10-28 14:47:57 -04:00
{
displayText: t('StudyDate'),
fieldName: 'studyDate',
inputType: 'date-range',
2019-10-28 14:47:57 -04:00
size: 300,
},
2019-10-28 14:47:57 -04:00
{
displayText: t('Modality'),
fieldName: 'modalities',
inputType: 'text',
size: 114,
},
2019-10-28 14:47:57 -04:00
{
displayText: t('StudyDescription'),
fieldName: 'studyDescription',
inputType: 'text',
size: 335,
},
2019-10-28 14:47:57 -04:00
];
2019-08-13 15:36:46 -04:00
2019-10-28 14:47:57 -04:00
const mediumTableMeta = [
2019-08-13 15:36:46 -04:00
{
displayText: `${t('Patient')} / ${t('MRN')}`,
2019-10-28 14:47:57 -04:00
fieldName: 'patientNameOrId',
inputType: 'text',
size: 250,
2019-08-13 15:36:46 -04:00
},
{
displayText: t('Description'),
2019-10-28 14:47:57 -04:00
fieldName: 'accessionOrModalityOrDescription',
inputType: 'text',
size: 350,
2019-08-13 15:36:46 -04:00
},
{
2019-10-28 14:47:57 -04:00
displayText: t('StudyDate'),
fieldName: 'studyDate',
inputType: 'date-range',
size: 300,
2019-08-13 15:36:46 -04:00
},
];
2019-10-28 14:47:57 -04:00
const smallTableMeta = [
{
displayText: t('Search'),
2019-10-28 14:47:57 -04:00
fieldName: 'allFields',
inputType: 'text',
size: 100,
},
];
const tableMeta = getContentFromUseMediaValue(
displaySize,
{ large: largeTableMeta, medium: mediumTableMeta, small: smallTableMeta },
2019-10-28 14:47:57 -04:00
smallTableMeta
);
2019-10-28 14:47:57 -04:00
const totalSize = tableMeta
.map(field => field.size)
.reduce((prev, next) => prev + next);
return translationsAreReady ? (
2019-10-28 14:47:57 -04:00
<table className="table table--striped table--hoverable">
<colgroup>
{tableMeta.map((field, i) => {
const size = field.size;
const percentWidth = (size / totalSize) * 100.0;
return <col key={i} style={{ width: `${percentWidth}%` }} />;
})}
</colgroup>
<thead className="table-head">
<tr className="filters">
<TableSearchFilter
meta={tableMeta}
values={filterValues}
onSort={handleSort}
onValueChange={handleFilterChange}
sortFieldName={sort.fieldName}
sortDirection={sort.direction}
studyListDateFilterNumDays={studyListDateFilterNumDays}
/>
</tr>
</thead>
<tbody className="table-body" data-cy="study-list-results">
{/* I'm not in love with this approach, but it's the quickest way for now
*
* - Display different content based on loading, empty, results state
*
* This is not ideal because it create a jump in focus. For loading especially,
* We should keep our current results visible while we load the new ones.
*/}
{/* LOADING */}
{isLoading && (
<tr className="no-hover">
2019-10-29 19:05:36 +08:00
<td colSpan={tableMeta.length}>
2019-10-28 14:47:57 -04:00
<StudyListLoadingText />
</td>
</tr>
)}
2019-10-28 14:47:57 -04:00
{!isLoading && hasError && (
<tr className="no-hover">
2019-10-29 19:05:36 +08:00
<td colSpan={tableMeta.length}>
2019-10-28 14:47:57 -04:00
<div className="notFound">
{t('There was an error fetching studies')}
</div>
</td>
</tr>
)}
{/* EMPTY */}
{!isLoading && !studies.length && (
<tr className="no-hover">
2019-10-29 19:05:36 +08:00
<td colSpan={tableMeta.length}>
2019-10-28 14:47:57 -04:00
<div className="notFound">{t('No matching results')}</div>
</td>
</tr>
)}
{!isLoading &&
studies.map((study, index) => (
<TableRow
key={`${study.studyInstanceUid}-${index}`}
onClick={studyInstanceUid => handleSelectItem(studyInstanceUid)}
accessionNumber={study.accessionNumber || ''}
modalities={study.modalities}
patientId={study.patientId || ''}
patientName={study.patientName || ''}
studyDate={study.studyDate}
studyDescription={study.studyDescription || ''}
studyInstanceUid={study.studyInstanceUid}
t={t}
displaySize={displaySize}
2019-10-28 14:47:57 -04:00
/>
))}
</tbody>
</table>
) : null;
2019-10-28 14:47:57 -04:00
}
2019-10-28 14:47:57 -04:00
StudyList.propTypes = {
isLoading: PropTypes.bool.isRequired,
hasError: PropTypes.bool.isRequired,
studies: PropTypes.array.isRequired,
onSelectItem: PropTypes.func.isRequired,
// ~~ SORT
sort: PropTypes.shape({
fieldName: PropTypes.string,
direction: PropTypes.oneOf(['desc', 'asc', null]),
}).isRequired,
onSort: PropTypes.func.isRequired,
// ~~ FILTERS
filterValues: PropTypes.shape({
patientName: PropTypes.string.isRequired,
patientId: PropTypes.string.isRequired,
accessionNumber: PropTypes.string.isRequired,
studyDate: PropTypes.string.isRequired,
modalities: PropTypes.string.isRequired,
studyDescription: PropTypes.string.isRequired,
patientNameOrId: PropTypes.string.isRequired,
accessionOrModalityOrDescription: PropTypes.string.isRequired,
allFields: PropTypes.string.isRequired,
studyDateTo: PropTypes.any,
studyDateFrom: PropTypes.any,
2019-10-28 14:47:57 -04:00
}).isRequired,
onFilterChange: PropTypes.func.isRequired,
studyListDateFilterNumDays: PropTypes.number,
displaySize: PropTypes.string,
2019-10-28 14:47:57 -04:00
};
StudyList.defaultProps = {};
function TableRow(props) {
const {
accessionNumber,
isHighlighted,
modalities,
patientId,
patientName,
studyDate,
studyDescription,
studyInstanceUid,
onClick: handleClick,
t,
displaySize,
2019-10-28 14:47:57 -04:00
} = props;
const largeRowTemplate = (
<tr
onClick={() => handleClick(studyInstanceUid)}
className={classNames({ active: isHighlighted })}
>
<td className={classNames({ 'empty-value': !patientName })}>
2019-10-28 14:47:57 -04:00
{patientName || `(${t('Empty')})`}
</td>
<td>{patientId}</td>
<td>{accessionNumber}</td>
<td>{studyDate}</td>
<td className={classNames({ 'empty-value': !modalities })}>
{modalities || `(${t('Empty')})`}
</td>
2019-10-28 14:47:57 -04:00
<td>{studyDescription}</td>
</tr>
);
2019-10-28 14:47:57 -04:00
const mediumRowTemplate = (
<tr
onClick={() => handleClick(studyInstanceUid)}
className={classNames({ active: isHighlighted })}
>
<td className={classNames({ 'empty-value': !patientName })}>
2019-10-28 14:47:57 -04:00
{patientName || `(${t('Empty')})`}
<div style={{ color: '#60656f' }}>{patientId}</div>
</td>
<td>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
{/* DESCRIPTION */}
<div
className="hide-xs"
style={{
whiteSpace: 'pre-wrap',
flexGrow: 1,
}}
>
{studyDescription}
</div>
2019-08-13 15:36:46 -04:00
2019-10-28 14:47:57 -04:00
{/* MODALITY & ACCESSION */}
<div
style={{
display: 'flex',
flexDirection: 'column',
maxWidth: '80px',
width: '80px',
2019-10-28 14:47:57 -04:00
}}
>
<div
className={classNames({
modalities: modalities,
'empty-value': !modalities,
})}
2019-10-28 14:47:57 -04:00
aria-label={modalities}
title={modalities}
>
{modalities || `(${t('Empty')})`}
2019-10-28 14:47:57 -04:00
</div>
<div
style={{
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
overflow: 'hidden',
}}
aria-label={accessionNumber}
title={accessionNumber}
>
{accessionNumber}
</div>
</div>
</div>
</td>
{/* DATE */}
<td style={{ textAlign: 'center' }}>{studyDate}</td>
</tr>
);
2019-08-13 15:36:46 -04:00
2019-10-28 14:47:57 -04:00
const smallRowTemplate = (
<tr
onClick={() => handleClick(studyInstanceUid)}
className={classNames({ active: isHighlighted })}
>
<td style={{ position: 'relative', overflow: 'hidden' }}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
{/* NAME AND ID */}
<div
className={classNames({ 'empty-value': !patientName })}
2019-10-28 14:47:57 -04:00
style={{ width: '150px', minWidth: '150px' }}
>
<div style={{ fontWeight: 500, paddingTop: '3px' }}>
{patientName || `(${t('Empty')})`}
</div>
<div style={{ color: '#60656f' }}>{patientId}</div>
</div>
2019-08-13 15:36:46 -04:00
2019-10-28 14:47:57 -04:00
{/* DESCRIPTION */}
<div
className="hide-xs"
style={{
whiteSpace: 'pre-wrap',
flexGrow: 1,
paddingLeft: '35px',
}}
>
{studyDescription}
</div>
2019-08-13 15:36:46 -04:00
2019-10-28 14:47:57 -04:00
{/* MODALITY & DATE */}
<div
style={{
display: 'flex',
flexDirection: 'column',
maxWidth: '80px',
width: '80px',
2019-10-28 14:47:57 -04:00
}}
>
<div
className={classNames({
modalities: modalities,
'empty-value': !modalities,
})}
aria-label={modalities}
title={modalities}
2019-10-28 14:47:57 -04:00
>
{modalities || `(${t('Empty')})`}
2019-10-28 14:47:57 -04:00
</div>
<div>{studyDate}</div>
2019-08-13 15:36:46 -04:00
</div>
</div>
2019-10-28 14:47:57 -04:00
</td>
</tr>
);
2019-08-13 15:36:46 -04:00
const rowTemplate = getContentFromUseMediaValue(
displaySize,
{
large: largeRowTemplate,
medium: mediumRowTemplate,
small: smallRowTemplate,
},
2019-10-28 14:47:57 -04:00
smallRowTemplate
);
2019-08-13 15:36:46 -04:00
2019-10-28 14:47:57 -04:00
return rowTemplate;
2019-08-13 15:36:46 -04:00
}
2019-10-28 14:47:57 -04:00
TableRow.propTypes = {
accessionNumber: PropTypes.string.isRequired,
isHighlighted: PropTypes.bool,
modalities: PropTypes.string,
2019-10-28 14:47:57 -04:00
patientId: PropTypes.string.isRequired,
patientName: PropTypes.string.isRequired,
studyDate: PropTypes.string.isRequired,
studyDescription: PropTypes.string.isRequired,
studyInstanceUid: PropTypes.string.isRequired,
displaySize: PropTypes.string,
2019-10-28 14:47:57 -04:00
};
TableRow.defaultProps = {
isHighlighted: false,
};
export { StudyList };