Files
ohif-viewer/platform/ui/src/components/studyList/StudyList.js
T
Danny Brown a870b3cc60 fix: Issue branch from danny experimental changes pr 1128 (#1150)
* fix: study list translations

* Don't render until translations are ready

* Try to wait for translations to load

* Use null to render "nothing"

* Try toggling useSuspense off up a layer

* logging

* Remove useSuspense false flags

* DO NOT OPEN A PR WITH IT.
Experimental changes only.
Try to solve issues with useTranslation hooks
 #Please enter the commit message for your changes. Lines starting

* Remove unecessary changes

* feat: 🎸 useMedia hook to not use one more prop for upd state vl

* docs: Add license scan report and status (#1161)

Signed-off-by: fossabot <badges@fossa.io>

* fix: 🐛 Fix for JS breaking on header (#1164)

* feat: 🎸 Code review and refact

Revised code based on PRs(variable alias,...). Changed hook for
useMedia. Now, it provides two hooks: one to get displayMediaSize and
other to get Entity(component, objects...) based on displayMediaSize.
Implemented a similar solution for state manager to store
mediaQueryList(s) and displaySize for app.

* feat: 🎸 Fine tunning on mediaQuery value, fixed issue about it

* chore(release): publish [skip ci]

 - @ohif/extension-vtk@0.53.6
 - @ohif/ui@0.62.1
 - @ohif/viewer@2.8.2

* Add new modal service

* Change serviceManager prop to servicesManager

* CR Update: fix casing and add required proptypes to providers

* CR Update: Improve ohifmodal proptypes

* CR Update: Fix typo in extensionmanager

* CR Update: add default props to service and check service in provider

* Refactor modal provider to better use its own state

* ci: don't build our master branch (#1177)

* ci: don't build our master branch

* Add netlify-cli as a dev dependency

* ci: trying a sheltered merge flow for promotions

* Use modal instead of modal context

* Ci/promotable builds (#1179)

* ci: don't build our master branch

* Add netlify-cli as a dev dependency

* ci: trying a sheltered merge flow for promotions

* ci: try building with QUICK_BUILD flag

* Change modal children order

* Ci/promotable builds (#1180)

* ci: don't build our master branch

* Add netlify-cli as a dev dependency

* ci: trying a sheltered merge flow for promotions

* ci: try building with QUICK_BUILD flag

* Try using ~/repo prefix in command

* Ci/promotable builds (#1181)

* ci: don't build our master branch

* Add netlify-cli as a dev dependency

* ci: trying a sheltered merge flow for promotions

* ci: try building with QUICK_BUILD flag

* Try using ~/repo prefix in command

* ci: make sure netlify command is available

* Ci/promotable builds (#1182)

* ci: don't build our master branch

* Add netlify-cli as a dev dependency

* ci: trying a sheltered merge flow for promotions

* ci: try building with QUICK_BUILD flag

* Try using ~/repo prefix in command

* ci: make sure netlify command is available

* ci: use sudo for global command

* Fix OHIFModal proptypes

* Ci/promotable builds (#1183)

* ci: don't build our master branch

* Add netlify-cli as a dev dependency

* ci: trying a sheltered merge flow for promotions

* ci: try building with QUICK_BUILD flag

* Try using ~/repo prefix in command

* ci: make sure netlify command is available

* ci: use sudo for global command

* Inline personal access token w/ env var

* Ci/promotable builds (#1184)

* ci: don't build our master branch

* Add netlify-cli as a dev dependency

* ci: trying a sheltered merge flow for promotions

* ci: try building with QUICK_BUILD flag

* Try using ~/repo prefix in command

* ci: make sure netlify command is available

* ci: use sudo for global command

* Inline personal access token w/ env var

* ci: workaround for sudo limitations

* ci: restore release workflow (#1185)

* ci: don't build our master branch

* Add netlify-cli as a dev dependency

* ci: trying a sheltered merge flow for promotions

* ci: try building with QUICK_BUILD flag

* Try using ~/repo prefix in command

* ci: make sure netlify command is available

* ci: use sudo for global command

* Inline personal access token w/ env var

* ci: workaround for sudo limitations

* ci: restore release workflow

* Pass services to each module, improve tests

* Add servicesManager test and registerServices method

* Fix key warning of snackbar elements

* Remove netlify-cli; we';ll install this on CI server

* Update staging and prod netlify site IDs

* Clean up NPM_PUBLISH step

* Clean up DOCS_PUBLISH step

* Clean up Deploy workflow

* Custom executor to override cypress config

* Spacing

* Use an existing docker hub image

* Switch to npx instead of digging into npm bin location

* Remove e2e test before prod deploy

* Add workflow images

* docs: continous integrationn

* Add default props to modal

* chore(release): publish [skip ci]

 - @ohif/extension-cornerstone@1.5.1
 - @ohif/extension-vtk@0.53.7
 - @ohif/core@1.9.1
 - @ohif/ui@0.62.2
 - @ohif/viewer@2.8.3

* chore(release): publish [skip ci]

 - @ohif/extension-vtk@0.53.8
 - @ohif/core@1.10.0
 - @ohif/ui@0.62.3
 - @ohif/viewer@2.8.4

* ci: Redirect site traffic to index.html if file is not resolved

* ci: fix typo

* fix: 🐛 Code review. Remove 'global state' for displaySize

* fix: 🐛 Code review. Ref back to useMedia and pass value down

Code review. Ref back to useMedia and pass value down (components)
instead of creating a specialized hook to tied any component on it

* Merge from master. Missing files changes

* feat: 🎸 Code review. Fix issues with DatePicker

Fixed at least the minimum issue with datePicker and update some content
on every translation changed
2019-11-25 10:45:37 -05:00

410 lines
11 KiB
JavaScript

import './StudyList.styl';
import React from 'react';
import classNames from 'classnames';
import TableSearchFilter from './TableSearchFilter.js';
import PropTypes from 'prop-types';
import { StudyListLoadingText } from './StudyListLoadingText.js';
import { useTranslation } from 'react-i18next';
const getContentFromUseMediaValue = (
displaySize,
contentArrayMap,
defaultContent
) => {
const content =
displaySize in contentArrayMap
? contentArrayMap[displaySize]
: defaultContent;
return content;
};
/**
*
*
* @param {*} props
* @returns
*/
function StudyList(props) {
const {
isLoading,
hasError,
studies,
sort,
onSort: handleSort,
filterValues,
onFilterChange: handleFilterChange,
onSelectItem: handleSelectItem,
studyListDateFilterNumDays,
displaySize,
} = props;
const { t, ready: translationsAreReady } = useTranslation('StudyList');
const largeTableMeta = [
{
displayText: t('PatientName'),
fieldName: 'patientName',
inputType: 'text',
size: 330,
},
{
displayText: t('MRN'),
fieldName: 'patientId',
inputType: 'text',
size: 378,
},
{
displayText: t('AccessionNumber'),
fieldName: 'accessionNumber',
inputType: 'text',
size: 180,
},
{
displayText: t('StudyDate'),
fieldName: 'studyDate',
inputType: 'date-range',
size: 300,
},
{
displayText: t('Modality'),
fieldName: 'modalities',
inputType: 'text',
size: 114,
},
{
displayText: t('StudyDescription'),
fieldName: 'studyDescription',
inputType: 'text',
size: 335,
},
];
const mediumTableMeta = [
{
displayText: `${t('Patient')} / ${t('MRN')}`,
fieldName: 'patientNameOrId',
inputType: 'text',
size: 250,
},
{
displayText: t('Description'),
fieldName: 'accessionOrModalityOrDescription',
inputType: 'text',
size: 350,
},
{
displayText: t('StudyDate'),
fieldName: 'studyDate',
inputType: 'date-range',
size: 300,
},
];
const smallTableMeta = [
{
displayText: t('Search'),
fieldName: 'allFields',
inputType: 'text',
size: 100,
},
];
const tableMeta = getContentFromUseMediaValue(
displaySize,
{ large: largeTableMeta, medium: mediumTableMeta, small: smallTableMeta },
smallTableMeta
);
const totalSize = tableMeta
.map(field => field.size)
.reduce((prev, next) => prev + next);
return translationsAreReady ? (
<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">
<td colSpan={tableMeta.length}>
<StudyListLoadingText />
</td>
</tr>
)}
{!isLoading && hasError && (
<tr className="no-hover">
<td colSpan={tableMeta.length}>
<div className="notFound">
{t('There was an error fetching studies')}
</div>
</td>
</tr>
)}
{/* EMPTY */}
{!isLoading && !studies.length && (
<tr className="no-hover">
<td colSpan={tableMeta.length}>
<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}
/>
))}
</tbody>
</table>
) : null;
}
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,
}).isRequired,
onFilterChange: PropTypes.func.isRequired,
studyListDateFilterNumDays: PropTypes.number,
displaySize: PropTypes.string,
};
StudyList.defaultProps = {};
function TableRow(props) {
const {
accessionNumber,
isHighlighted,
modalities,
patientId,
patientName,
studyDate,
studyDescription,
studyInstanceUid,
onClick: handleClick,
t,
displaySize,
} = props;
const largeRowTemplate = (
<tr
onClick={() => handleClick(studyInstanceUid)}
className={classNames({ active: isHighlighted })}
>
<td className={classNames({ 'empty-value': !patientName })}>
{patientName || `(${t('Empty')})`}
</td>
<td>{patientId}</td>
<td>{accessionNumber}</td>
<td>{studyDate}</td>
<td className={classNames({ 'empty-value': !modalities })}>
{modalities || `(${t('Empty')})`}
</td>
<td>{studyDescription}</td>
</tr>
);
const mediumRowTemplate = (
<tr
onClick={() => handleClick(studyInstanceUid)}
className={classNames({ active: isHighlighted })}
>
<td className={classNames({ 'empty-value': !patientName })}>
{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>
{/* MODALITY & ACCESSION */}
<div
style={{
display: 'flex',
flexDirection: 'column',
maxWidth: '80px',
width: '80px',
}}
>
<div
className={classNames({
modalities: modalities,
'empty-value': !modalities,
})}
aria-label={modalities}
title={modalities}
>
{modalities || `(${t('Empty')})`}
</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>
);
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 })}
style={{ width: '150px', minWidth: '150px' }}
>
<div style={{ fontWeight: 500, paddingTop: '3px' }}>
{patientName || `(${t('Empty')})`}
</div>
<div style={{ color: '#60656f' }}>{patientId}</div>
</div>
{/* DESCRIPTION */}
<div
className="hide-xs"
style={{
whiteSpace: 'pre-wrap',
flexGrow: 1,
paddingLeft: '35px',
}}
>
{studyDescription}
</div>
{/* MODALITY & DATE */}
<div
style={{
display: 'flex',
flexDirection: 'column',
maxWidth: '80px',
width: '80px',
}}
>
<div
className={classNames({
modalities: modalities,
'empty-value': !modalities,
})}
aria-label={modalities}
title={modalities}
>
{modalities || `(${t('Empty')})`}
</div>
<div>{studyDate}</div>
</div>
</div>
</td>
</tr>
);
const rowTemplate = getContentFromUseMediaValue(
displaySize,
{
large: largeRowTemplate,
medium: mediumRowTemplate,
small: smallRowTemplate,
},
smallRowTemplate
);
return rowTemplate;
}
TableRow.propTypes = {
accessionNumber: PropTypes.string.isRequired,
isHighlighted: PropTypes.bool,
modalities: PropTypes.string,
patientId: PropTypes.string.isRequired,
patientName: PropTypes.string.isRequired,
studyDate: PropTypes.string.isRequired,
studyDescription: PropTypes.string.isRequired,
studyInstanceUid: PropTypes.string.isRequired,
displaySize: PropTypes.string,
};
TableRow.defaultProps = {
isHighlighted: false,
};
export { StudyList };