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:
Gustavo André Lelis 2020-03-12 13:26:31 -03:00 committed by James A. Petts
parent 2049147021
commit 2138565990
8 changed files with 317 additions and 68 deletions

View File

@ -4,6 +4,7 @@ export {
Icon,
IconButton,
Svg,
Input,
ThemeWrapper,
Typography,
} from './src/components';

View File

@ -61,7 +61,7 @@ const variantClasses = {
};
const sizeClasses = {
small: 'py-2 px-2 text-base',
small: 'py-2 px-2 text-sm',
medium: 'py-2 px-2 text-lg',
large: 'py-2 px-6 text-xl',
};
@ -97,12 +97,13 @@ const Button = ({
return (
<button
className={classnames(
className,
baseClasses,
variantClasses[variant][color],
radiusClasses[radius],
sizeClasses[size],
disabledClasses[disabled],
className
fullWidthClasses[fullWidth],
disabledClasses[disabled]
)}
type={type}
{...rest}
@ -119,7 +120,14 @@ Button.propTypes = {
size: PropTypes.oneOf(['small', 'medium', 'large']),
radius: PropTypes.oneOf(['none', 'small', 'medium', 'large', 'full']),
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,
type: PropTypes.string,
startIcon: PropTypes.node,

View File

@ -127,7 +127,13 @@ IconButton.propTypes = {
size: PropTypes.oneOf(['small', 'medium', 'large']),
rounded: PropTypes.oneOf(['none', 'small', 'medium', 'large', 'full']),
variant: PropTypes.oneOf(['text', 'outlined', 'contained']),
color: PropTypes.oneOf(['default', 'primary', 'secondary', 'white']),
color: PropTypes.oneOf([
'default',
'primary',
'secondary',
'white',
'inherit',
]),
fullWidth: PropTypes.bool,
disabled: PropTypes.bool,
type: PropTypes.string,

View File

@ -1,50 +1,54 @@
import React from 'react';
import PropTypes from 'prop-types';
import Label from '../Label';
import classnames from 'classnames';
const Input = ({
label,
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 baseInputClasses =
'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';
const transparentClasses = {
true: 'bg-transparent',
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;

View File

@ -1,32 +1,18 @@
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
const Label = ({ children, spacing, className, text, ...rest }) => {
const baseClasses = 'flex flex-1 flex-col text-gray-700 text-sm font-bold';
const getClasses = () => {
const classes = [];
classes.push(baseClasses);
classes.push(className);
return classes.join(' ');
};
const Label = ({ children, className, text, ...rest }) => {
const baseClasses = '';
return (
<label className={getClasses()} {...rest}>
<label className={classnames(baseClasses, className)} {...rest}>
{text}
{children}
</label>
);
};
const spacing = {
small: 'mb-1',
medium: 'mb-2',
large: 'mb-3',
};
Label.defaultProps = {};
Label.propTypes = {
children: PropTypes.node,
};

View File

@ -2,8 +2,18 @@ import Button from './Button';
import ButtonGroup from './ButtonGroup';
import Icon from './Icon';
import IconButton from './IconButton';
import Input from './Input';
import Svg from './Svg';
import ThemeWrapper from './ThemeWrapper/';
import Typography from './Typography';
export { Button, ButtonGroup, Icon, IconButton, Svg, ThemeWrapper, Typography };
export {
Button,
ButtonGroup,
Icon,
IconButton,
Input,
Svg,
ThemeWrapper,
Typography,
};

View File

@ -12,7 +12,7 @@ function Header({ appLogo = OHIFLogo(), children, t }) {
};
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="mx-3">{appLogo}</div>
<div>{children}</div>

View File

@ -1,7 +1,241 @@
import React from 'react';
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
const StudyListFilter = () => {
return <div>{`<- STUDYLIST FILTER CONTENT ->`}</div>;
import { Button, Icon, Input, Typography } from '@ohif/ui';
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;