feat: Study List Filter (#1504)
* Inital changes for study list filters * Style updates on filter * Small style changes on filter * Style changes updates on Input and Label and finish fitler * Refactor on study list filter * Remove not used callbackFunction * Small style change * Adding underline on Learn more link * Make sure does not show sortable if have no results * Some style polish * Fixing margin on filter * Adding inherit as color on buttons * Adding inherit on learn more button color * Addin border with opacity
This commit is contained in:
parent
2049147021
commit
2138565990
@ -4,6 +4,7 @@ export {
|
|||||||
Icon,
|
Icon,
|
||||||
IconButton,
|
IconButton,
|
||||||
Svg,
|
Svg,
|
||||||
|
Input,
|
||||||
ThemeWrapper,
|
ThemeWrapper,
|
||||||
Typography,
|
Typography,
|
||||||
} from './src/components';
|
} from './src/components';
|
||||||
|
|||||||
@ -61,7 +61,7 @@ const variantClasses = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const sizeClasses = {
|
const sizeClasses = {
|
||||||
small: 'py-2 px-2 text-base',
|
small: 'py-2 px-2 text-sm',
|
||||||
medium: 'py-2 px-2 text-lg',
|
medium: 'py-2 px-2 text-lg',
|
||||||
large: 'py-2 px-6 text-xl',
|
large: 'py-2 px-6 text-xl',
|
||||||
};
|
};
|
||||||
@ -97,12 +97,13 @@ const Button = ({
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={classnames(
|
className={classnames(
|
||||||
|
className,
|
||||||
baseClasses,
|
baseClasses,
|
||||||
variantClasses[variant][color],
|
variantClasses[variant][color],
|
||||||
radiusClasses[radius],
|
radiusClasses[radius],
|
||||||
sizeClasses[size],
|
sizeClasses[size],
|
||||||
disabledClasses[disabled],
|
fullWidthClasses[fullWidth],
|
||||||
className
|
disabledClasses[disabled]
|
||||||
)}
|
)}
|
||||||
type={type}
|
type={type}
|
||||||
{...rest}
|
{...rest}
|
||||||
@ -119,7 +120,14 @@ Button.propTypes = {
|
|||||||
size: PropTypes.oneOf(['small', 'medium', 'large']),
|
size: PropTypes.oneOf(['small', 'medium', 'large']),
|
||||||
radius: PropTypes.oneOf(['none', 'small', 'medium', 'large', 'full']),
|
radius: PropTypes.oneOf(['none', 'small', 'medium', 'large', 'full']),
|
||||||
variant: PropTypes.oneOf(['text', 'outlined', 'contained']),
|
variant: PropTypes.oneOf(['text', 'outlined', 'contained']),
|
||||||
color: PropTypes.oneOf(['default', 'primary', 'secondary']),
|
color: PropTypes.oneOf([
|
||||||
|
'default',
|
||||||
|
'primary',
|
||||||
|
'secondary',
|
||||||
|
'white',
|
||||||
|
'inherit',
|
||||||
|
]),
|
||||||
|
fullWidth: PropTypes.bool,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
type: PropTypes.string,
|
type: PropTypes.string,
|
||||||
startIcon: PropTypes.node,
|
startIcon: PropTypes.node,
|
||||||
|
|||||||
@ -127,7 +127,13 @@ IconButton.propTypes = {
|
|||||||
size: PropTypes.oneOf(['small', 'medium', 'large']),
|
size: PropTypes.oneOf(['small', 'medium', 'large']),
|
||||||
rounded: PropTypes.oneOf(['none', 'small', 'medium', 'large', 'full']),
|
rounded: PropTypes.oneOf(['none', 'small', 'medium', 'large', 'full']),
|
||||||
variant: PropTypes.oneOf(['text', 'outlined', 'contained']),
|
variant: PropTypes.oneOf(['text', 'outlined', 'contained']),
|
||||||
color: PropTypes.oneOf(['default', 'primary', 'secondary', 'white']),
|
color: PropTypes.oneOf([
|
||||||
|
'default',
|
||||||
|
'primary',
|
||||||
|
'secondary',
|
||||||
|
'white',
|
||||||
|
'inherit',
|
||||||
|
]),
|
||||||
fullWidth: PropTypes.bool,
|
fullWidth: PropTypes.bool,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
type: PropTypes.string,
|
type: PropTypes.string,
|
||||||
|
|||||||
@ -1,50 +1,54 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import Label from '../Label';
|
import Label from '../Label';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
const Input = ({
|
const baseInputClasses =
|
||||||
label,
|
'shadow transition duration-300 appearance-none border rounded w-full py-2 px-3 text-sm text-gray-700 hover:border-gray-500 leading-tight focus:border-gray-500 focus:outline-none';
|
||||||
containerClassName = '',
|
|
||||||
labelClassName = '',
|
|
||||||
className = '',
|
|
||||||
transparent = defaults.transparent,
|
|
||||||
...rest
|
|
||||||
}) => {
|
|
||||||
const getClasses = () => {
|
|
||||||
const classes = [];
|
|
||||||
|
|
||||||
classes.push(transparentClasses[transparent]);
|
|
||||||
|
|
||||||
return classes.join(' ');
|
|
||||||
};
|
|
||||||
|
|
||||||
const input = (
|
|
||||||
<input
|
|
||||||
className={`shadow transition duration-300 appearance-none border rounded w-full py-2 px-3 text-sm text-gray-700 hover:border-gray-500 leading-tight focus:border-gray-500 focus:outline-none ${getClasses()} ${className}`}
|
|
||||||
id="id"
|
|
||||||
{...rest}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderElement = () => {
|
|
||||||
return label ? (
|
|
||||||
<Label className={labelClassName} text="Label">
|
|
||||||
{input}
|
|
||||||
</Label>
|
|
||||||
) : (
|
|
||||||
input
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return <div className={`flex ${containerClassName}`}>{renderElement()}</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaults = {
|
|
||||||
transparent: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const transparentClasses = {
|
const transparentClasses = {
|
||||||
true: 'bg-transparent',
|
true: 'bg-transparent',
|
||||||
false: '',
|
false: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Input = ({
|
||||||
|
label,
|
||||||
|
containerClassName = '',
|
||||||
|
labelClassName = '',
|
||||||
|
className = '',
|
||||||
|
transparent = true,
|
||||||
|
type = 'text',
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
...otherProps
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className={classnames('flex flex-col flex-1', containerClassName)}>
|
||||||
|
<Label className={labelClassName} text={label}></Label>
|
||||||
|
<input
|
||||||
|
className={classnames(
|
||||||
|
className,
|
||||||
|
baseInputClasses,
|
||||||
|
transparentClasses[transparent]
|
||||||
|
)}
|
||||||
|
type={type}
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
{...otherProps}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Input.propTypes = {
|
||||||
|
label: PropTypes.string,
|
||||||
|
containerClassName: PropTypes.string,
|
||||||
|
labelClassName: PropTypes.string,
|
||||||
|
className: PropTypes.string,
|
||||||
|
transparent: PropTypes.bool,
|
||||||
|
type: PropTypes.string,
|
||||||
|
value: PropTypes.any,
|
||||||
|
onChange: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
export default Input;
|
export default Input;
|
||||||
|
|||||||
@ -1,32 +1,18 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
const Label = ({ children, spacing, className, text, ...rest }) => {
|
const Label = ({ children, className, text, ...rest }) => {
|
||||||
const baseClasses = 'flex flex-1 flex-col text-gray-700 text-sm font-bold';
|
const baseClasses = '';
|
||||||
|
|
||||||
const getClasses = () => {
|
|
||||||
const classes = [];
|
|
||||||
classes.push(baseClasses);
|
|
||||||
classes.push(className);
|
|
||||||
return classes.join(' ');
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<label className={getClasses()} {...rest}>
|
<label className={classnames(baseClasses, className)} {...rest}>
|
||||||
{text}
|
{text}
|
||||||
{children}
|
{children}
|
||||||
</label>
|
</label>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const spacing = {
|
|
||||||
small: 'mb-1',
|
|
||||||
medium: 'mb-2',
|
|
||||||
large: 'mb-3',
|
|
||||||
};
|
|
||||||
|
|
||||||
Label.defaultProps = {};
|
|
||||||
|
|
||||||
Label.propTypes = {
|
Label.propTypes = {
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,8 +2,18 @@ import Button from './Button';
|
|||||||
import ButtonGroup from './ButtonGroup';
|
import ButtonGroup from './ButtonGroup';
|
||||||
import Icon from './Icon';
|
import Icon from './Icon';
|
||||||
import IconButton from './IconButton';
|
import IconButton from './IconButton';
|
||||||
|
import Input from './Input';
|
||||||
import Svg from './Svg';
|
import Svg from './Svg';
|
||||||
import ThemeWrapper from './ThemeWrapper/';
|
import ThemeWrapper from './ThemeWrapper/';
|
||||||
import Typography from './Typography';
|
import Typography from './Typography';
|
||||||
|
|
||||||
export { Button, ButtonGroup, Icon, IconButton, Svg, ThemeWrapper, Typography };
|
export {
|
||||||
|
Button,
|
||||||
|
ButtonGroup,
|
||||||
|
Icon,
|
||||||
|
IconButton,
|
||||||
|
Input,
|
||||||
|
Svg,
|
||||||
|
ThemeWrapper,
|
||||||
|
Typography,
|
||||||
|
};
|
||||||
|
|||||||
@ -12,7 +12,7 @@ function Header({ appLogo = OHIFLogo(), children, t }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-custom-navy flex flex-row justify-between p-2 text-white">
|
<div className="bg-custom-navy flex flex-row justify-between px-3 py-1 text-white">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="mx-3">{appLogo}</div>
|
<div className="mx-3">{appLogo}</div>
|
||||||
<div>{children}</div>
|
<div>{children}</div>
|
||||||
|
|||||||
@ -1,7 +1,241 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
const StudyListFilter = () => {
|
import { Button, Icon, Input, Typography } from '@ohif/ui';
|
||||||
return <div>{`<- STUDYLIST FILTER CONTENT ->`}</div>;
|
|
||||||
|
const sortIconMap = {
|
||||||
|
'-1': 'sorting-active-down',
|
||||||
|
0: 'sorting',
|
||||||
|
1: 'sorting-active-up',
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
numOfStudies: 0,
|
||||||
|
filterMeta: [
|
||||||
|
{
|
||||||
|
name: 'patientName',
|
||||||
|
displayName: 'Patient Name',
|
||||||
|
inputType: 'text',
|
||||||
|
isSortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mrn',
|
||||||
|
displayName: 'MRN',
|
||||||
|
inputType: 'text',
|
||||||
|
isSortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'studyDate',
|
||||||
|
displayName: 'Study date',
|
||||||
|
inputType: 'text',
|
||||||
|
isSortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'description',
|
||||||
|
displayName: 'Description',
|
||||||
|
inputType: 'text',
|
||||||
|
isSortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'modality',
|
||||||
|
displayName: 'Modality',
|
||||||
|
inputType: 'text',
|
||||||
|
isSortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accession',
|
||||||
|
displayName: 'Accession',
|
||||||
|
inputType: 'text',
|
||||||
|
isSortable: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
filtersValues: {
|
||||||
|
patientName: '',
|
||||||
|
mrn: '',
|
||||||
|
studyDate: '',
|
||||||
|
description: '',
|
||||||
|
modality: '',
|
||||||
|
accession: '',
|
||||||
|
sortBy: '',
|
||||||
|
sortDirection: 0,
|
||||||
|
page: 0,
|
||||||
|
resultsPerPage: 25,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const FilterLabel = ({
|
||||||
|
label = '',
|
||||||
|
isSortable = false,
|
||||||
|
isBeingSorted = false,
|
||||||
|
sortDirection = 0,
|
||||||
|
onLabelClick,
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
|
const handleLabelClick = () => {
|
||||||
|
if (onLabelClick) {
|
||||||
|
onLabelClick();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const iconProps = {
|
||||||
|
name: isBeingSorted ? sortIconMap[sortDirection] : 'sorting',
|
||||||
|
colorClass: isBeingSorted ? 'text-custom-aquaBright' : 'text-custom-blue',
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<label
|
||||||
|
className={classnames(
|
||||||
|
'flex flex-col flex-1 text-white text-lg pl-1',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className="flex flex-row" onClick={handleLabelClick}>
|
||||||
|
{label}
|
||||||
|
{isSortable && (
|
||||||
|
<Icon
|
||||||
|
name={iconProps.name}
|
||||||
|
className={classnames('mx-2 w-2', iconProps.colorClass)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
{children}
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const StudyListFilter = ({
|
||||||
|
filtersMeta = defaultProps.filterMeta,
|
||||||
|
filtersValues = defaultProps.filtersValues,
|
||||||
|
numOfStudies = 0,
|
||||||
|
}) => {
|
||||||
|
const [currentFiltersValues, setcurrentFiltersValues] = useState(
|
||||||
|
filtersValues
|
||||||
|
);
|
||||||
|
const { sortBy, sortDirection } = currentFiltersValues;
|
||||||
|
|
||||||
|
const handleFilterLabelClick = name => {
|
||||||
|
if (numOfStudies <= 100) {
|
||||||
|
setcurrentFiltersValues(prevState => ({
|
||||||
|
...prevState,
|
||||||
|
sortBy: name,
|
||||||
|
sortDirection: sortDirection === 1 || sortBy === name ? -1 : 1,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFilterValueChange = (event, name) => {
|
||||||
|
const { value } = event.target;
|
||||||
|
setcurrentFiltersValues(prevState => ({
|
||||||
|
...prevState,
|
||||||
|
[name]: value,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearFilters = () => {
|
||||||
|
const _filterValues = { ...currentFiltersValues };
|
||||||
|
filtersMeta.forEach(filter => {
|
||||||
|
if (_filterValues[filter.name]) {
|
||||||
|
delete _filterValues[filter.name];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setcurrentFiltersValues(_filterValues);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isFiltering = () => {
|
||||||
|
return filtersMeta.some(filter => {
|
||||||
|
const filterValue = currentFiltersValues[filter.name];
|
||||||
|
return filterValue && filterValue !== '';
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-custom-navyDark">
|
||||||
|
<div className="container m-auto relative flex flex-col pt-5 pb-3 px-4">
|
||||||
|
<div className="flex flex-row justify-between mb-5">
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<Typography variant="h4" className="text-custom-aquaBright mr-6">
|
||||||
|
Study List
|
||||||
|
</Typography>
|
||||||
|
<div className="flex flex-row items-end">
|
||||||
|
<Button
|
||||||
|
variant="text"
|
||||||
|
size="small"
|
||||||
|
color="inherit"
|
||||||
|
className="text-custom-blueBright"
|
||||||
|
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-custom-blueBright"></span>
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row items-baseline">
|
||||||
|
{isFiltering() && (
|
||||||
|
<Button
|
||||||
|
rounded="full"
|
||||||
|
variant="outlined"
|
||||||
|
color="primary"
|
||||||
|
className="text-custom-blueBright border-custom-blueBright mx-8"
|
||||||
|
startIcon={<Icon name="cancel" />}
|
||||||
|
onClick={clearFilters}
|
||||||
|
>
|
||||||
|
Clear filters
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Typography variant="h4" className="text-white mr-2">
|
||||||
|
{numOfStudies > 100 ? '>100' : numOfStudies}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h6" className="text-custom-grayLight">
|
||||||
|
Studies
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row">
|
||||||
|
{filtersMeta.map(({ name, displayName, inputType, isSortable }) => {
|
||||||
|
return (
|
||||||
|
<FilterLabel
|
||||||
|
key={name}
|
||||||
|
label={displayName}
|
||||||
|
isSortable={
|
||||||
|
isSortable && numOfStudies <= 100 && numOfStudies > 0
|
||||||
|
}
|
||||||
|
isBeingSorted={sortBy === name}
|
||||||
|
sortDirection={sortDirection}
|
||||||
|
onLabelClick={() => handleFilterLabelClick(name)}
|
||||||
|
inputType={inputType}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
className="border-custom-blue mt-2 bg-black"
|
||||||
|
type="text"
|
||||||
|
containerClassName="mr-2"
|
||||||
|
value={currentFiltersValues[name] || ''}
|
||||||
|
onChange={event => handleFilterValueChange(event, name)}
|
||||||
|
/>
|
||||||
|
</FilterLabel>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<label className="text-white text-sm pl-1 flex flex-1">
|
||||||
|
Instances
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
StudyListFilter.propTypes = {
|
||||||
|
filtersMeta: PropTypes.arrayOf({
|
||||||
|
name: PropTypes.string,
|
||||||
|
dsplayName: PropTypes.string,
|
||||||
|
inputType: PropTypes.oneOf(['text', 'select', 'date-range', 'none']),
|
||||||
|
isSortable: PropTypes.bool,
|
||||||
|
}),
|
||||||
|
filtersValues: PropTypes.object,
|
||||||
|
numOfStudies: PropTypes.number,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default StudyListFilter;
|
export default StudyListFilter;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user