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,
|
||||
IconButton,
|
||||
Svg,
|
||||
Input,
|
||||
ThemeWrapper,
|
||||
Typography,
|
||||
} from './src/components';
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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,
|
||||
};
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user