feat: OHIF-120 - Study List Route Template (#1693)
* fix pagination size * clean up, start a very basic route configuration * quick navigation between routes * configure webpack to read .env variables * export studylist filter * split studylist filter into a component * minor fix in studylist pagination * update studylistfilter imports * create studylist container with study list content * moving file * study list filter doc page * catch change in ui-v2 * fix: 1px gap that shows text underneath * Shift to routes folder Co-authored-by: Danny Brown <danny.ri.brown@gmail.com>
This commit is contained in:
1 parent
01b314522d
commit
cee09fcf8d
11 files changed
+460
-30
No files matched your search
@@ -0,0 +1,129 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Button, Icon, Typography, InputGroup } from '@ohif/ui';
|
||||
|
||||
const StudyListFilter = ({
|
||||
filtersMeta,
|
||||
filterValues,
|
||||
setFilterValues,
|
||||
clearFilters,
|
||||
isFiltering,
|
||||
numOfStudies,
|
||||
}) => {
|
||||
const { sortBy, sortDirection } = filterValues;
|
||||
const filterSorting = { sortBy, sortDirection };
|
||||
const setFilterSorting = sortingValues => {
|
||||
setFilterValues({
|
||||
...filterValues,
|
||||
...sortingValues,
|
||||
});
|
||||
};
|
||||
const isSortingEnable = numOfStudies > 0 && numOfStudies <= 100;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div>
|
||||
<div className="bg-primary-dark">
|
||||
<div className="container m-auto relative flex flex-col pt-5">
|
||||
<div className="flex flex-row justify-between mb-5 px-12">
|
||||
<div className="flex flex-row">
|
||||
<Typography variant="h4" className="text-primary-light mr-6">
|
||||
Study List
|
||||
</Typography>
|
||||
<div className="flex flex-row items-end">
|
||||
<Button
|
||||
variant="text"
|
||||
size="small"
|
||||
color="inherit"
|
||||
className="text-primary-active"
|
||||
startIcon={<Icon name="info-link" className="w-2" />}
|
||||
>
|
||||
<span className="flex flex-col flex-1">
|
||||
<span>Learn more</span>
|
||||
<span className="opacity-50 pt-1 border-b border-primary-active"></span>
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row">
|
||||
{isFiltering && (
|
||||
<Button
|
||||
rounded="full"
|
||||
variant="outlined"
|
||||
color="primary"
|
||||
className="text-primary-active border-primary-active mx-8"
|
||||
startIcon={<Icon name="cancel" />}
|
||||
onClick={clearFilters}
|
||||
>
|
||||
Clear filters
|
||||
</Button>
|
||||
)}
|
||||
<Typography variant="h4" className="mr-2">
|
||||
{numOfStudies > 100 ? '>100' : numOfStudies}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h6"
|
||||
className="text-common-light self-end pb-1"
|
||||
>
|
||||
Studies
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="sticky z-10 border-b-4 border-black"
|
||||
style={{ top: '57px' }}
|
||||
>
|
||||
<div className="bg-primary-dark pt-3 pb-3 ">
|
||||
<InputGroup
|
||||
inputMeta={filtersMeta}
|
||||
values={filterValues}
|
||||
onValuesChange={setFilterValues}
|
||||
sorting={filterSorting}
|
||||
onSortingChange={setFilterSorting}
|
||||
isSortingEnable={isSortingEnable}
|
||||
/>
|
||||
</div>
|
||||
{numOfStudies > 100 && (
|
||||
<div className="container m-auto">
|
||||
<div className="bg-primary-main text-center text-base py-1 rounded-b">
|
||||
<p className="text-white">
|
||||
Filter list to 100 studies or less to enable sorting
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
StudyListFilter.propTypes = {
|
||||
filtersMeta: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
displayName: PropTypes.string.isRequired,
|
||||
inputType: PropTypes.oneOf(['Text', 'MultiSelect', 'DateRange', 'None'])
|
||||
.isRequired,
|
||||
isSortable: PropTypes.bool.isRequired,
|
||||
gridCol: PropTypes.oneOf([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
|
||||
.isRequired,
|
||||
option: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
value: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
})
|
||||
),
|
||||
})
|
||||
).isRequired,
|
||||
filterValues: PropTypes.object.isRequired,
|
||||
numOfStudies: PropTypes.number.isRequired,
|
||||
setFilterValues: PropTypes.func.isRequired,
|
||||
clearFilters: PropTypes.func.isRequired,
|
||||
isFiltering: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default StudyListFilter;
|
||||
@@ -0,0 +1,118 @@
|
||||
---
|
||||
name: Study List Filter
|
||||
menu: Data Display
|
||||
route: components/studyListFilter
|
||||
---
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Playground, Props } from 'docz';
|
||||
import { StudyListFilter } from '@ohif/ui';
|
||||
|
||||
# Study List Filter
|
||||
|
||||
## Import
|
||||
|
||||
```javascript
|
||||
import { StudyListFilter } from '@ohif/ui';
|
||||
```
|
||||
|
||||
## Basic usage
|
||||
|
||||
<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 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];
|
||||
});
|
||||
};
|
||||
return (
|
||||
<StudyListFilter
|
||||
numOfStudies={100}
|
||||
filtersMeta={filtersMeta}
|
||||
filterValues={filterValues}
|
||||
setFilterValues={setFilterValues}
|
||||
clearFilters={() => setFilterValues(defaultFilterValues)}
|
||||
isFiltering={isFiltering(filterValues, defaultFilterValues)}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Playground>
|
||||
|
||||
## Properties
|
||||
|
||||
<Props of={StudyListFilter} />
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './StudyListFilter';
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, ButtonGroup, Icon, IconButton, Typography } from '@ohif/ui';
|
||||
import { Button, ButtonGroup, Icon, Typography } from '@ohif/ui';
|
||||
|
||||
const StudyListPagination = ({
|
||||
onChangePage,
|
||||
@@ -21,8 +21,7 @@ const StudyListPagination = ({
|
||||
<div className="relative mr-3">
|
||||
<select
|
||||
defaultValue={perPage}
|
||||
className="block appearance-none w-full bg-transparent border border-custom-darkSlateBlue text-white text-base px-2 pr-4 rounded leading-tight focus:outline-none"
|
||||
style={{ height: 28 }}
|
||||
className="block appearance-none w-full bg-transparent border border-common-active text-white text-base px-2 pr-4 rounded leading-tight focus:outline-none h-8"
|
||||
onChange={e => onChangePerPage(e.target.value)}
|
||||
onBlur={() => {}}
|
||||
>
|
||||
@@ -48,22 +47,22 @@ const StudyListPagination = ({
|
||||
Page {currentPage}
|
||||
</Typography>
|
||||
<ButtonGroup color="primary">
|
||||
<IconButton
|
||||
size="inherit"
|
||||
<Button
|
||||
size="initial"
|
||||
className="border-common-active px-4 py-2 text-base"
|
||||
color="white"
|
||||
onClick={() => navigateToPage(1)}
|
||||
>
|
||||
<>{`<<`}</>
|
||||
</IconButton>
|
||||
{`<<`}
|
||||
</Button>
|
||||
<Button
|
||||
size="inherit"
|
||||
size="initial"
|
||||
className="border-common-active py-2 px-2 text-base"
|
||||
color="white"
|
||||
onClick={() => navigateToPage(currentPage - 1)}
|
||||
>{`< Previous`}</Button>
|
||||
<Button
|
||||
size="inherit"
|
||||
size="initial"
|
||||
className="border-common-active py-2 px-4 text-base"
|
||||
color="white"
|
||||
onClick={() => navigateToPage(currentPage + 1)}
|
||||
|
||||
@@ -24,6 +24,7 @@ import StudyListPagination from './StudyListPagination';
|
||||
import StudyListTable from './StudyListTable';
|
||||
import Svg from './Svg';
|
||||
import StudyItem from './StudyItem';
|
||||
import StudyListFilter from './StudyListFilter';
|
||||
import Table from './Table';
|
||||
import TableBody from './TableBody';
|
||||
import TableCell from './TableCell';
|
||||
@@ -66,6 +67,7 @@ export {
|
||||
StudyBrowser,
|
||||
StudyItem,
|
||||
StudyListExpandedRow,
|
||||
StudyListFilter,
|
||||
StudyListPagination,
|
||||
StudyListTable,
|
||||
Svg,
|
||||
|
||||
Reference in new issue
Block a user