* ohif-333: add component foundation * ohif-333: add expandable content and mocked data * ohif-333: improve ListMenu component for the new toolbar button and add radio behavior * ohif-333: add click outside behavior and update primary and secondary props * ohif-333: separate classes from markup * ohif-333: fix layout issues * ohif-333: extract window level component * ohif-333: update component returns * ohif-333: update chevron thickness * ohif-333: remove is active prop * Delete lerna-debug.log * ohif-333: clean toolbar and use example in ui package * ohif-333: update styles to avoid word break * ohif-333: fix ui package build Co-authored-by: Erik Ziegler <erik.sweed@gmail.com> Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
348 lines
9.4 KiB
Plaintext
348 lines
9.4 KiB
Plaintext
---
|
|
name: Study List
|
|
menu: Views
|
|
route: examples/studyList
|
|
---
|
|
|
|
import { useState } from 'react';
|
|
import { Playground } from 'docz';
|
|
import {
|
|
Icon,
|
|
StudyListExpandedRow,
|
|
Button,
|
|
NavBar,
|
|
Svg,
|
|
IconButton,
|
|
EmptyStudies,
|
|
StudyListTable,
|
|
StudyListPagination,
|
|
StudyListFilter
|
|
} from '../../components';
|
|
import utils from '../../utils';
|
|
|
|
|
|
import classnames from 'classnames';
|
|
import moment from 'moment';
|
|
|
|
# Study List
|
|
|
|
This example shows you how you can build a Study List page using the available
|
|
components.
|
|
|
|
<Playground>
|
|
{() => {
|
|
const defaultFilterValues = {
|
|
patientName: '',
|
|
mrn: '',
|
|
studyDate: {
|
|
startDate: null,
|
|
endDate: null,
|
|
},
|
|
description: '',
|
|
modality: undefined,
|
|
accession: '',
|
|
sortBy: '',
|
|
sortDirection: 'none',
|
|
page: 0,
|
|
resultsPerPage: 25,
|
|
};
|
|
const [filterValues, setFilterValues] = useState(defaultFilterValues);
|
|
const [studies, setStudies] = useState([]);
|
|
const numOfStudies = studies.length;
|
|
const [expandedRows, setExpandedRows] = useState([]);
|
|
const filtersMeta = [
|
|
{
|
|
name: 'patientName',
|
|
displayName: 'Patient Name',
|
|
inputType: 'Text',
|
|
isSortable: true,
|
|
gridCol: 4,
|
|
},
|
|
{
|
|
name: 'mrn',
|
|
displayName: 'MRN',
|
|
inputType: 'Text',
|
|
isSortable: true,
|
|
gridCol: 2,
|
|
},
|
|
{
|
|
name: 'studyDate',
|
|
displayName: 'Study date',
|
|
inputType: 'DateRange',
|
|
isSortable: true,
|
|
gridCol: 5,
|
|
},
|
|
{
|
|
name: 'description',
|
|
displayName: 'Description',
|
|
inputType: 'Text',
|
|
isSortable: true,
|
|
gridCol: 4,
|
|
},
|
|
{
|
|
name: 'modality',
|
|
displayName: 'Modality',
|
|
inputType: 'MultiSelect',
|
|
inputProps: {
|
|
options: [
|
|
{ value: 'SEG', label: 'SEG' },
|
|
{ value: 'CT', label: 'CT' },
|
|
{ value: 'MR', label: 'MR' },
|
|
{ value: 'SR', label: 'SR' },
|
|
],
|
|
},
|
|
isSortable: true,
|
|
gridCol: 3,
|
|
},
|
|
{
|
|
name: 'accession',
|
|
displayName: 'Accession',
|
|
inputType: 'Text',
|
|
isSortable: true,
|
|
gridCol: 4,
|
|
},
|
|
{
|
|
name: 'instances',
|
|
displayName: 'Instances',
|
|
inputType: 'None',
|
|
isSortable: true,
|
|
gridCol: 2,
|
|
},
|
|
];
|
|
const isFiltering = (filterValues, defaultFilterValues) => {
|
|
return Object.keys(defaultFilterValues).some((name) => {
|
|
return filterValues[name] !== defaultFilterValues[name];
|
|
});
|
|
};
|
|
const tableDataSource = studies.map((study, key) => {
|
|
const rowKey = key + 1;
|
|
const isExpanded = expandedRows.some((k) => k === rowKey);
|
|
const {
|
|
AccessionNumber,
|
|
Modalities,
|
|
Instances,
|
|
StudyDescription,
|
|
PatientId,
|
|
PatientName,
|
|
StudyDate,
|
|
series,
|
|
} = study;
|
|
const seriesTableColumns = {
|
|
description: 'Description',
|
|
seriesNumber: 'Series',
|
|
modality: 'Modality',
|
|
Instances: 'Instances',
|
|
};
|
|
const seriesTableDataSource = series.map((seriesItem) => {
|
|
const { SeriesNumber, Modality, instances } = seriesItem;
|
|
return {
|
|
description: 'Patient Protocol',
|
|
seriesNumber: SeriesNumber,
|
|
modality: Modality,
|
|
Instances: instances.length,
|
|
};
|
|
});
|
|
return {
|
|
row: [
|
|
{
|
|
key: 'patientName',
|
|
content: PatientName,
|
|
gridCol: 4,
|
|
},
|
|
{
|
|
key: 'mrn',
|
|
content: PatientId,
|
|
gridCol: 2,
|
|
},
|
|
{
|
|
key: 'studyDate',
|
|
content: (
|
|
<div>
|
|
<span className="mr-4">
|
|
{moment(StudyDate).format('MMM-DD-YYYY')}
|
|
</span>
|
|
<span>{moment(StudyDate).format('hh:mm A')}</span>
|
|
</div>
|
|
),
|
|
gridCol: 5,
|
|
},
|
|
{
|
|
key: 'description',
|
|
content: StudyDescription,
|
|
gridCol: 4,
|
|
},
|
|
{
|
|
key: 'modality',
|
|
content: Modalities,
|
|
gridCol: 3,
|
|
},
|
|
{
|
|
key: 'accession',
|
|
content: AccessionNumber,
|
|
gridCol: 4,
|
|
},
|
|
{
|
|
key: 'instances',
|
|
content: (
|
|
<>
|
|
<Icon
|
|
name="group-layers"
|
|
className={classnames('inline-flex mr-2 w-4', {
|
|
'text-primary-active': isExpanded,
|
|
'text-secondary-light': !isExpanded,
|
|
})}
|
|
/>
|
|
{Instances}
|
|
</>
|
|
),
|
|
gridCol: 4,
|
|
},
|
|
],
|
|
expandedContent: (
|
|
<StudyListExpandedRow
|
|
seriesTableColumns={seriesTableColumns}
|
|
seriesTableDataSource={seriesTableDataSource}
|
|
>
|
|
<Button
|
|
rounded="full"
|
|
variant="contained"
|
|
className="mr-4 font-bold"
|
|
endIcon={
|
|
<Icon name="launch-arrow" style={{ color: '#21a7c6' }} />
|
|
}
|
|
>
|
|
Basic Viewer
|
|
</Button>
|
|
<Button
|
|
rounded="full"
|
|
variant="contained"
|
|
className="mr-4 font-bold"
|
|
endIcon={
|
|
<Icon name="launch-arrow" style={{ color: '#21a7c6' }} />
|
|
}
|
|
>
|
|
Segmentation
|
|
</Button>
|
|
<Button
|
|
rounded="full"
|
|
variant="outlined"
|
|
endIcon={<Icon name="launch-info" />}
|
|
className="font-bold"
|
|
>
|
|
Module 3
|
|
</Button>
|
|
<div className="ml-5 text-lg text-common-bright inline-flex items-center">
|
|
<Icon
|
|
name="notificationwarning-diamond"
|
|
className="mr-2 w-5 h-5"
|
|
/>
|
|
Feedback text lorem ipsum dolor sit amet
|
|
</div>
|
|
</StudyListExpandedRow>
|
|
),
|
|
onClickRow: () =>
|
|
setExpandedRows((s) =>
|
|
isExpanded ? s.filter((n) => rowKey !== n) : [...s, rowKey]
|
|
),
|
|
isExpanded,
|
|
};
|
|
});
|
|
const handleStudyList = (number) => {
|
|
const studies = utils.getMockedStudies(number);
|
|
setStudies(studies);
|
|
setCurrentPage(1);
|
|
};
|
|
const [currentPage, setCurrentPage] = useState(1);
|
|
const [perPage, setPerPage] = useState(25);
|
|
const totalPages = Math.floor(numOfStudies / perPage);
|
|
const onChangePage = (page) => {
|
|
if (page > totalPages) {
|
|
return;
|
|
}
|
|
setCurrentPage(page);
|
|
};
|
|
const onChangePerPage = (perPage) => {
|
|
setPerPage(perPage);
|
|
setCurrentPage(1);
|
|
};
|
|
const hasStudies = numOfStudies > 0;
|
|
return (
|
|
<div>
|
|
<div className="bg-white p-4">
|
|
<Button className="mr-2" onClick={() => handleStudyList(0)}>
|
|
0 Studies
|
|
</Button>
|
|
<Button className="mr-2" onClick={() => handleStudyList(50)}>
|
|
50 Studies
|
|
</Button>
|
|
<Button className="mr-2" onClick={() => handleStudyList(100)}>
|
|
100 Studies
|
|
</Button>
|
|
<Button className="mr-2" onClick={() => handleStudyList(1000)}>
|
|
1000 Studies
|
|
</Button>
|
|
</div>
|
|
<div
|
|
className={classnames('bg-black h-full', {
|
|
'h-screen': !hasStudies,
|
|
})}
|
|
>
|
|
<NavBar className="justify-between border-b-4 border-black" isSticky>
|
|
<div className="flex items-center">
|
|
<div className="mx-3">
|
|
<Svg name="logo-ohif" />
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center">
|
|
<span className="mr-3 text-common-light text-lg">
|
|
FOR INVESTIGATIONAL USE ONLY
|
|
</span>
|
|
<IconButton
|
|
variant="text"
|
|
color="inherit"
|
|
className="text-primary-active"
|
|
onClick={() => {}}
|
|
>
|
|
<React.Fragment>
|
|
<Icon name="settings" />
|
|
<Icon name="chevron-down" />
|
|
</React.Fragment>
|
|
</IconButton>
|
|
</div>
|
|
</NavBar>
|
|
<StudyListFilter
|
|
numOfStudies={numOfStudies}
|
|
filtersMeta={filtersMeta}
|
|
filterValues={filterValues}
|
|
onChange={setFilterValues}
|
|
clearFilters={() => setFilterValues(defaultFilterValues)}
|
|
isFiltering={isFiltering(filterValues, defaultFilterValues)}
|
|
/>
|
|
{hasStudies ? (
|
|
<>
|
|
<StudyListTable
|
|
tableDataSource={tableDataSource.slice(
|
|
(currentPage - 1) * perPage,
|
|
(currentPage - 1) * perPage + perPage
|
|
)}
|
|
numOfStudies={numOfStudies}
|
|
filtersMeta={filtersMeta}
|
|
/>
|
|
<StudyListPagination
|
|
onChangePage={onChangePage}
|
|
onChangePerPage={onChangePerPage}
|
|
currentPage={currentPage}
|
|
perPage={perPage}
|
|
/>
|
|
</>
|
|
) : (
|
|
<div className="flex flex-col items-center justify-center pt-48">
|
|
<EmptyStudies />
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}}
|
|
</Playground>
|