refactor: StudyListFilter refactor (#1580)
* Creating InputGroup * Updating Views with new InputGroup * Creating Docz for InputGroup * Fixing eslint issues * Renaming inputGroup props naming to be more generic.
This commit is contained in:
1 parent
5766d4e91a
commit
056f56f5b4
13 files changed
+418
-97
No files matched your search
@@ -4,36 +4,52 @@ import 'react-dates/lib/css/_datepicker.css';
|
||||
import { DateRangePicker, isInclusivelyBeforeDay } from 'react-dates';
|
||||
import './DateRange.css';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import moment from 'moment';
|
||||
|
||||
const DateRange = (props) => {
|
||||
const today = moment();
|
||||
const lastWeek = moment().subtract(7, 'day');
|
||||
const lastMonth = moment().subtract(1, 'month');
|
||||
const studyDatePresets = [
|
||||
{
|
||||
text: 'Today',
|
||||
start: today,
|
||||
end: today,
|
||||
},
|
||||
{
|
||||
text: 'Last 7 days',
|
||||
start: lastWeek,
|
||||
end: today,
|
||||
},
|
||||
{
|
||||
text: 'Last 30 days',
|
||||
start: lastMonth,
|
||||
end: today,
|
||||
},
|
||||
];
|
||||
|
||||
const renderYearsOptions = () => {
|
||||
const currentYear = moment().year();
|
||||
const options = [];
|
||||
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const year = currentYear - i;
|
||||
options.push(
|
||||
<option key={year} value={year}>
|
||||
{year}
|
||||
</option>
|
||||
);
|
||||
}
|
||||
|
||||
return options;
|
||||
};
|
||||
|
||||
const DateRange = props => {
|
||||
const { onChange, startDate, endDate } = props;
|
||||
const [focusedInput, setFocusedInput] = useState(null);
|
||||
|
||||
const today = moment();
|
||||
const lastWeek = moment().subtract(7, 'day');
|
||||
const lastMonth = moment().subtract(1, 'month');
|
||||
|
||||
const studyDatePresets = [
|
||||
{
|
||||
text: 'Today',
|
||||
start: today,
|
||||
end: today,
|
||||
},
|
||||
{
|
||||
text: 'Last 7 days',
|
||||
start: lastWeek,
|
||||
end: today,
|
||||
},
|
||||
{
|
||||
text: 'Last 30 days',
|
||||
start: lastMonth,
|
||||
end: today,
|
||||
},
|
||||
];
|
||||
const renderYearsOptionsCallback = useCallback(renderYearsOptions, []);
|
||||
|
||||
const renderDatePresets = () => {
|
||||
return (
|
||||
@@ -60,35 +76,30 @@ const DateRange = (props) => {
|
||||
);
|
||||
};
|
||||
const renderMonthElement = ({ month, onMonthSelect, onYearSelect }) => {
|
||||
const renderYearsOptions = () => {
|
||||
const yearsRange = 20;
|
||||
const options = [];
|
||||
|
||||
for (let i = 0; i < yearsRange; i++) {
|
||||
const year = moment().year() - i;
|
||||
options.push(
|
||||
<option key={year} value={year}>
|
||||
{year}
|
||||
</option>
|
||||
);
|
||||
}
|
||||
|
||||
return options;
|
||||
};
|
||||
|
||||
renderMonthElement.propTypes = {
|
||||
month: PropTypes.object,
|
||||
onMonthSelect: PropTypes.func,
|
||||
onYearSelect: PropTypes.func,
|
||||
};
|
||||
|
||||
const handleMonthChange = event => {
|
||||
onMonthSelect(month, event.target.value);
|
||||
};
|
||||
|
||||
const handleYearChange = event => {
|
||||
onYearSelect(month, event.target.value);
|
||||
};
|
||||
|
||||
const handleOnBlur = () => {};
|
||||
|
||||
return (
|
||||
<div className="flex justify-center">
|
||||
<div className="my-0 mx-1">
|
||||
<select
|
||||
className="DateRangePicker_select"
|
||||
value={month.month()}
|
||||
onChange={(e) => onMonthSelect(month, e.target.value)}
|
||||
onChange={handleMonthChange}
|
||||
onBlur={handleOnBlur}
|
||||
>
|
||||
{moment.months().map((label, value) => (
|
||||
<option key={value} value={value}>
|
||||
@@ -98,13 +109,13 @@ const DateRange = (props) => {
|
||||
</select>
|
||||
</div>
|
||||
<div className="my-0 mx-1">
|
||||
{}
|
||||
<select
|
||||
className="DateRangePicker_select"
|
||||
value={month.year()}
|
||||
onChange={(e) => onYearSelect(month, e.target.value)}
|
||||
onChange={handleYearChange}
|
||||
onBlur={handleOnBlur}
|
||||
>
|
||||
{renderYearsOptions()}
|
||||
{renderYearsOptionsCallback()}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -120,7 +131,7 @@ const DateRange = (props) => {
|
||||
endDateId={'endDateId'}
|
||||
onDatesChange={onChange}
|
||||
focusedInput={focusedInput}
|
||||
onFocusChange={(updatedVal) => setFocusedInput(updatedVal)}
|
||||
onFocusChange={updatedVal => setFocusedInput(updatedVal)}
|
||||
/** OPTIONAL */
|
||||
renderCalendarInfo={renderDatePresets}
|
||||
renderMonthElement={renderMonthElement}
|
||||
@@ -130,7 +141,7 @@ const DateRange = (props) => {
|
||||
closeDatePicker: 'Close',
|
||||
clearDates: 'Clear dates',
|
||||
}}
|
||||
isOutsideRange={(day) => !isInclusivelyBeforeDay(day, moment())}
|
||||
isOutsideRange={day => !isInclusivelyBeforeDay(day, moment())}
|
||||
hideKeyboardShortcutsPanel={true}
|
||||
numberOfMonths={1}
|
||||
showClearDates={false}
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import {
|
||||
InputText,
|
||||
InputDateRange,
|
||||
InputMultiSelect,
|
||||
InputLabelWrapper,
|
||||
} from '@ohif/ui';
|
||||
|
||||
const InputGroup = ({
|
||||
inputMeta,
|
||||
values,
|
||||
onValuesChange,
|
||||
sorting,
|
||||
onSortingChange,
|
||||
isSortingEnable,
|
||||
}) => {
|
||||
const { sortBy, sortDirection } = sorting;
|
||||
|
||||
const handleFilterLabelClick = name => {
|
||||
if (isSortingEnable) {
|
||||
let _sortDirection = 'ascending';
|
||||
if (sortBy === name) {
|
||||
if (sortDirection === 'ascending') {
|
||||
_sortDirection = 'descending';
|
||||
} else if (sortDirection === 'descending') {
|
||||
_sortDirection = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
onSortingChange({
|
||||
sortBy: _sortDirection !== 'none' ? name : '',
|
||||
sortDirection: _sortDirection,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const renderFieldInputComponent = ({
|
||||
name,
|
||||
displayName,
|
||||
inputProps,
|
||||
isSortable,
|
||||
inputType,
|
||||
}) => {
|
||||
const _isSortable = isSortable && isSortingEnable;
|
||||
const _sortDirection = sortBy !== name ? 'none' : sortDirection;
|
||||
|
||||
const onLabelClick = () => {
|
||||
handleFilterLabelClick(name);
|
||||
};
|
||||
|
||||
const handleFieldChange = newValue => {
|
||||
onValuesChange({
|
||||
...values,
|
||||
[name]: newValue,
|
||||
});
|
||||
};
|
||||
|
||||
switch (inputType) {
|
||||
case 'Text':
|
||||
return (
|
||||
<InputText
|
||||
key={name}
|
||||
label={displayName}
|
||||
isSortable={_isSortable}
|
||||
sortDirection={_sortDirection}
|
||||
onLabelClick={onLabelClick}
|
||||
value={values[name]}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
);
|
||||
case 'MultiSelect':
|
||||
return (
|
||||
<InputMultiSelect
|
||||
key={name}
|
||||
label={displayName}
|
||||
isSortable={_isSortable}
|
||||
sortDirection={_sortDirection}
|
||||
onLabelClick={onLabelClick}
|
||||
value={values[name]}
|
||||
onChange={handleFieldChange}
|
||||
options={inputProps.options}
|
||||
/>
|
||||
);
|
||||
case 'DateRange':
|
||||
return (
|
||||
<InputDateRange
|
||||
key={name}
|
||||
label={displayName}
|
||||
isSortable={_isSortable}
|
||||
sortDirection={_sortDirection}
|
||||
onLabelClick={onLabelClick}
|
||||
value={values[name]}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
);
|
||||
case 'None':
|
||||
return (
|
||||
<InputLabelWrapper
|
||||
key={name}
|
||||
label={displayName}
|
||||
isSortable={_isSortable}
|
||||
sortDirection={_sortDirection}
|
||||
onLabelClick={onLabelClick}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="container m-auto relative flex flex-col">
|
||||
<div className="flex flex-row w-full">
|
||||
{inputMeta.map(inputMeta => {
|
||||
return (
|
||||
<div
|
||||
key={inputMeta.name}
|
||||
className={classnames(
|
||||
'pl-4 first:pl-12',
|
||||
`w-${inputMeta.gridCol}/24`
|
||||
)}
|
||||
>
|
||||
{renderFieldInputComponent(inputMeta)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
InputGroup.propTypes = {
|
||||
inputMeta: 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,
|
||||
values: PropTypes.object.isRequired,
|
||||
onValuesChange: PropTypes.func.isRequired,
|
||||
sorting: PropTypes.shape({
|
||||
sortBy: PropTypes.string,
|
||||
sortDirection: PropTypes.oneOf(['ascending', 'descending', 'none']),
|
||||
}).isRequired,
|
||||
onSortingChange: PropTypes.func.isRequired,
|
||||
isSortingEnable: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default InputGroup;
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
name: InputGroup
|
||||
menu: Components
|
||||
route: components/InputGroup
|
||||
---
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Playground, Props } from 'docz';
|
||||
import { InputGroup } from '@ohif/ui';
|
||||
|
||||
# Input Group
|
||||
|
||||
## Import
|
||||
|
||||
```javascript
|
||||
import { InputGroup } from '@ohif/ui';
|
||||
```
|
||||
|
||||
## Basic usage
|
||||
|
||||
<Playground>
|
||||
{() => {
|
||||
const [filterValues, setFilterValues] = useState({
|
||||
patient: '',
|
||||
studyDate: {
|
||||
startDate: null,
|
||||
endDate: null,
|
||||
},
|
||||
modality: [],
|
||||
});
|
||||
const [filterSorting, setFilterSorting] = useState({
|
||||
sortBy: '',
|
||||
sortDirection: 'none',
|
||||
});
|
||||
const filtersMeta = [
|
||||
{
|
||||
name: 'patient',
|
||||
displayName: 'Patient Name',
|
||||
inputType: 'Text',
|
||||
isSortable: true,
|
||||
gridCol: 6,
|
||||
},
|
||||
{
|
||||
name: 'studyDate',
|
||||
displayName: 'Study date',
|
||||
inputType: 'DateRange',
|
||||
isSortable: true,
|
||||
gridCol: 6,
|
||||
},
|
||||
{
|
||||
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: 6,
|
||||
},
|
||||
{
|
||||
name: 'numberOfInstance',
|
||||
displayName: 'Number of Instances',
|
||||
inputType: 'None',
|
||||
isSortable: true,
|
||||
gridCol: 6,
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div className="py-4">
|
||||
<InputGroup
|
||||
inputMeta={filtersMeta}
|
||||
values={filterValues}
|
||||
onValuesChange={setFilterValues}
|
||||
sorting={filterSorting}
|
||||
onSortingChange={setFilterSorting}
|
||||
isSortingEnable={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Playground>
|
||||
|
||||
## Properties
|
||||
|
||||
<Props of={InputGroup} />
|
||||
@@ -0,0 +1,2 @@
|
||||
import InputGroup from './InputGroup';
|
||||
export default InputGroup;
|
||||
@@ -24,9 +24,11 @@ const InputLabelWrapper = ({
|
||||
return (
|
||||
<label className={classnames(baseLabelClassName, className)}>
|
||||
<span
|
||||
role="button"
|
||||
className={spanClassName}
|
||||
onClick={onLabelClick}
|
||||
onKeyDown={onLabelClick}
|
||||
tabIndex="0"
|
||||
>
|
||||
{label}
|
||||
{isSortable && (
|
||||
|
||||
@@ -59,6 +59,7 @@ InputMultiSelect.propTypes = {
|
||||
sortDirection: PropTypes.oneOf(['ascending', 'descending', 'none'])
|
||||
.isRequired,
|
||||
onLabelClick: PropTypes.func.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
placeholder: PropTypes.string,
|
||||
options: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
@@ -78,7 +79,6 @@ InputMultiSelect.propTypes = {
|
||||
label: PropTypes.string,
|
||||
}),
|
||||
]),
|
||||
onChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default InputMultiSelect;
|
||||
@@ -6,9 +6,12 @@ import Icon from './Icon';
|
||||
import IconButton from './IconButton';
|
||||
import Input from './Input';
|
||||
import InputDateRange from './InputDateRange';
|
||||
import InputGroup from './InputGroup';
|
||||
import InputLabelWrapper from './InputLabelWrapper';
|
||||
import InputMultiSelect from './InputMultiSelect';
|
||||
import InputText from './InputText';
|
||||
import Label from './Label';
|
||||
import NavBar from './NavBar';
|
||||
import Select from './Select';
|
||||
import Svg from './Svg';
|
||||
import Table from './Table';
|
||||
@@ -28,9 +31,12 @@ export {
|
||||
IconButton,
|
||||
Input,
|
||||
InputDateRange,
|
||||
InputGroup,
|
||||
InputLabelWrapper,
|
||||
InputMultiSelect,
|
||||
InputText,
|
||||
Label,
|
||||
NavBar,
|
||||
Select,
|
||||
Svg,
|
||||
Table,
|
||||
|
||||
Reference in new issue
Block a user