Commit 89b22e48 authored by Maxym Borodenko's avatar Maxym Borodenko
Browse files

StringArrFilter: Indented categories

parent 67e4bb05
...@@ -65,6 +65,7 @@ const AccessionFilters = ({handleSubmit, initialValues, initialize, terms, crops ...@@ -65,6 +65,7 @@ const AccessionFilters = ({handleSubmit, initialValues, initialize, terms, crops
name="sampStat" name="sampStat"
options={ Accession.SAMPSTAT } options={ Accession.SAMPSTAT }
byKey byKey
indented
terms={ terms && terms.get('sampStat') } terms={ terms && terms.get('sampStat') }
/> />
</CollapsibleComponentSearch> </CollapsibleComponentSearch>
...@@ -73,6 +74,7 @@ const AccessionFilters = ({handleSubmit, initialValues, initialize, terms, crops ...@@ -73,6 +74,7 @@ const AccessionFilters = ({handleSubmit, initialValues, initialize, terms, crops
name="storage" name="storage"
options={ Accession.STORAGE } options={ Accession.STORAGE }
byKey byKey
indented
terms={ terms && terms.get('storage') } terms={ terms && terms.get('storage') }
/> />
</CollapsibleComponentSearch> </CollapsibleComponentSearch>
......
import * as React from 'react';
import { reduxForm } from 'redux-form';
import {translate} from 'react-i18next';
import { ACCESSION_MAP_FILTERFORM } from 'accessions/constants';
import FiltersBlock from 'ui/common/filter/FiltersBlock';
import CollapsibleComponentSearch from 'ui/common/filter/CollapsibleComponentSearch';
import BooleanFilter from 'ui/common/filter/BooleanFilter';
import NumberFilter from 'ui/common/filter/NumberFilter';
import StringFilter from 'ui/common/filter/StringFilter';
import StringArrFilter from 'ui/common/filter/StringArrFilter';
import Accession from 'model/accession/Accession';
import DateFilter from 'ui/common/filter/DateFilter';
import CropFilter from 'crop/ui/c/CropFilter';
const MapAccessionsFilters = ({handleSubmit, initialValues, initialize, t, ...other}) => {
// console.log('AccessionFilters', initialValues);
return (
<FiltersBlock title={ t('accessions.public.f.filtersTitle') } handleSubmit={ handleSubmit } initialize={ initialize } { ...other }>
<CollapsibleComponentSearch title={ t('accessions.public.f.historic') }>
<BooleanFilter name="historic"/>
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title={ t('common:f.textSearch') }>
<StringArrFilter name="holder.code" label={ t('accessions.common.instituteCode') } placeholder="NGA039"/>
<StringFilter name="acceNumb" searchType="contains" label={ t('accessions.common.acceNumb') } placeholder="IRGC"/>
<NumberFilter name="seqNo" label={ t('accessions.public.f.seqNumber') } />
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title={ t('common:f.dateSearch') }>
<DateFilter name="lastModifiedDate" label={ t('common:f.lastModifiedDate') }/>
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title={ t('accessions.public.f.crop') }>
<CropFilter/>
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title={ t('accessions.common.taxonomy') }>
<StringArrFilter name="taxa.genus" label={ t('accessions.common.genus') } placeholder="Hordeum"/>
<StringArrFilter name="taxa.species" label={ t('accessions.common.species') } placeholder="vulgare"/>
<StringFilter name="taxa.subtaxa" searchType="contains" label={ t('accessions.public.f.subtaxon') } placeholder=""/>
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title={ t('accessions.public.f.originOfMaterial') }>
<StringArrFilter name="origin.iso3" label={ t('accessions.common.countryOfOrigin') } placeholder="SVN"/>
<NumberFilter name="geo.latitude" label={ t('geo.common.latitude') } />
<NumberFilter name="geo.longitude" label={ t('geo.common.longitude') } />
<NumberFilter name="geo.elevation" label={ t('accessions.public.f.elevation') } />
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title={ t('accessions.public.f.climate') }>
<NumberFilter name="geo.climate.bio1" label={ t('accessions.climate.bio1') } />
<NumberFilter name="geo.climate.bio2" label={ t('accessions.climate.bio2') } />
<NumberFilter name="geo.climate.bio3" label={ t('accessions.climate.bio3') } />
<NumberFilter name="geo.climate.bio4" label={ t('accessions.climate.bio4') } />
<NumberFilter name="geo.climate.bio5" label={ t('accessions.climate.bio5') } />
<NumberFilter name="geo.climate.bio6" label={ t('accessions.climate.bio6') } />
<NumberFilter name="geo.climate.bio7" label={ t('accessions.climate.bio7') } />
<NumberFilter name="geo.climate.bio8" label={ t('accessions.climate.bio8') } />
<NumberFilter name="geo.climate.bio9" label={ t('accessions.climate.bio9') } />
<NumberFilter name="geo.climate.bio10" label={ t('accessions.climate.bio10') } />
<NumberFilter name="geo.climate.bio11" label={ t('accessions.climate.bio11') } />
<NumberFilter name="geo.climate.bio12" label={ t('accessions.climate.bio12') } />
<NumberFilter name="geo.climate.bio13" label={ t('accessions.climate.bio13') } />
<NumberFilter name="geo.climate.bio14" label={ t('accessions.climate.bio14') } />
<NumberFilter name="geo.climate.bio15" label={ t('accessions.climate.bio15') } />
<NumberFilter name="geo.climate.bio16" label={ t('accessions.climate.bio16') } />
<NumberFilter name="geo.climate.bio17" label={ t('accessions.climate.bio17') } />
<NumberFilter name="geo.climate.bio18" label={ t('accessions.climate.bio18') } />
<NumberFilter name="geo.climate.bio19" label={ t('accessions.climate.bio19') } />
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title={ t('accessions.common.sampStat') }>
<StringArrFilter name="sampStat" options={ Accession.SAMPSTAT } />
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title={ t('accessions.common.storageType') }>
<StringArrFilter name="storage" options={ Accession.STORAGE } />
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title={ t('accessions.public.f.status') }>
<BooleanFilter name="historic" label={ t('accessions.public.f.historic') } />
<BooleanFilter name="available" label={ t('accessions.public.f.available') } />
<BooleanFilter name="mlsStatus" label={ t('accessions.public.f.mlsStatus') } />
<BooleanFilter name="sgsv" label={ t('accessions.public.f.sgsv') } />
<BooleanFilter name="images" label={ t('accessions.public.f.images') } />
</CollapsibleComponentSearch>
</FiltersBlock>
);
};
export default translate()(reduxForm({
enableReinitialize: true,
destroyOnUnmount: false,
form: ACCESSION_MAP_FILTERFORM,
})(MapAccessionsFilters));
...@@ -26,7 +26,7 @@ class StatusFilter extends React.Component<IStatusFilter, any> { ...@@ -26,7 +26,7 @@ class StatusFilter extends React.Component<IStatusFilter, any> {
public render() { public render() {
return ( return (
<StringArrFilter name="state" options={ this.options }/> <StringArrFilter name="state" options={ this.options } byKey/>
); );
} }
} }
......
import * as React from 'react'; import * as React from 'react';
import { Fields, change, FieldArray } from 'redux-form'; import { Fields, change } from 'redux-form';
import {connect} from 'react-redux'; import {connect} from 'react-redux';
import {bindActionCreators} from 'redux'; import {bindActionCreators} from 'redux';
import { translate } from 'react-i18next'; import { translate } from 'react-i18next';
import * as _ from 'lodash'; import * as _ from 'lodash';
import FormGroup from '@material-ui/core/FormGroup';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import FormLabel from '@material-ui/core/FormLabel';
import FormControl from '@material-ui/core/FormControl'; import FormControl from '@material-ui/core/FormControl';
import Checkbox from '@material-ui/core/Checkbox';
import IconButton from '@material-ui/core/IconButton'; import IconButton from '@material-ui/core/IconButton';
import Input from '@material-ui/core/Input'; import Input from '@material-ui/core/Input';
import InputLabel from '@material-ui/core/InputLabel'; import InputLabel from '@material-ui/core/InputLabel';
import InputAdornment from '@material-ui/core/InputAdornment'; import InputAdornment from '@material-ui/core/InputAdornment';
import PlusOne from '@material-ui/icons/PlusOne'; import PlusOne from '@material-ui/icons/PlusOne';
import CheckboxSelection from '../forms/CheckboxSelection';
import Number from 'ui/common/Number'; import Number from 'ui/common/Number';
import {Properties, PropertiesItem} from 'ui/common/Properties'; import { Properties, PropertiesItem } from 'ui/common/Properties';
interface IStringListProps extends React.ClassAttributes<any> { interface IStringListProps extends React.ClassAttributes<any> {
input: any; input: any;
...@@ -119,6 +122,7 @@ interface IStringArrFilterInternal extends React.ClassAttributes<any> { ...@@ -119,6 +122,7 @@ interface IStringArrFilterInternal extends React.ClassAttributes<any> {
placeholder?: string; placeholder?: string;
label?: string; label?: string;
options?: { [key: string]: any; }; options?: { [key: string]: any; };
indented?: boolean;
t: any; t: any;
addToNotList: (item: any) => void; addToNotList: (item: any) => void;
notList: any[]; notList: any[];
...@@ -162,13 +166,18 @@ class InternalStringArrField extends React.Component<IStringArrFilterInternal & ...@@ -162,13 +166,18 @@ class InternalStringArrField extends React.Component<IStringArrFilterInternal &
}); });
} }
private maybeAdd = (text: string) => { private maybeAdd = (...newValues: string[]) => {
const values = [ ...this.state.values ]; const values = [ ...this.state.values ];
if (text && text.length > 0) { newValues.forEach((text) => {
if (values.indexOf(text) < 0) { if (text && text.length > 0) {
values.push(text); if (values.indexOf(text) < 0) {
values.push(text);
}
} }
});
if (!_.isEqual(values, this.state.values)) {
this.setState({ this.setState({
text: '', text: '',
values, values,
...@@ -177,14 +186,19 @@ class InternalStringArrField extends React.Component<IStringArrFilterInternal & ...@@ -177,14 +186,19 @@ class InternalStringArrField extends React.Component<IStringArrFilterInternal &
return values; return values;
} }
private maybeRemove = (text: string) => { private maybeRemove = (...newValues: string[]) => {
const values = [ ...this.state.values ]; const values = [ ...this.state.values ];
if (text && text.length > 0) { newValues.forEach((text) => {
const index: number = values.indexOf(text); if (text && text.length > 0) {
if (index >= 0) { const index: number = values.indexOf(text);
values.splice(index, 1); if (index >= 0) {
values.splice(index, 1);
}
} }
});
if (!_.isEqual(values, this.state.values)) {
this.setState({ this.setState({
text: '', text: '',
values, values,
...@@ -230,46 +244,117 @@ class InternalStringArrField extends React.Component<IStringArrFilterInternal & ...@@ -230,46 +244,117 @@ class InternalStringArrField extends React.Component<IStringArrFilterInternal &
input.onChange(values); input.onChange(values);
} }
private handleCheckbox = (event) => {
const { input } = _.get(this.props, this.props.names[0]);
const value = event.target.value;
if (this.props.indented) {
const subCategories = this.getSubcategories(value);
const values = event.target.checked ? this.maybeAdd(value, ...subCategories) : this.maybeRemove(value);
input.onChange(values);
} else {
const values = event.target.checked ? this.maybeAdd(value) : this.maybeRemove(value);
input.onChange(values);
}
}
private getSubcategories = (value) => {
const myRegexp = /^([1-9]+)(0+)$/;
const match = myRegexp.exec(value);
const category = match ? match[1] : null;
const subCategories = [];
const filteredOptions = this.getFilteredOptions();
Object.keys(this.props.options).forEach((option) => {
if (category && option !== value && filteredOptions.indexOf(option) > 0 && option.startsWith(category)) {
subCategories.push(option);
}
});
return subCategories;
}
private getFilteredOptions = () => {
const { options, byKey, terms, valueField } = this.props;
const fieldOptions = options && (byKey ? Object.keys(options) : options);
return terms ? (fieldOptions && fieldOptions.filter((opt) => terms.get(valueField ? opt[valueField] : `${opt}`) > 0)) : fieldOptions;
}
private buildMapOfCategories = (map: Map<string, number>, options: string[], level: number) => {
options.forEach((option) => {
const subCategories = this.getSubcategories(option);
if (subCategories.length === 0) {
if (map.has(option)) {
const oldLevel = map.get(option);
if (oldLevel <= level) {
map.set(option, level);
}
} else {
map.set(option, level);
}
} else {
if (map.has(option)) {
const oldLevel = map.get(option);
if (oldLevel <= level) {
map.set(option, level);
this.buildMapOfCategories(map, subCategories, level + 1);
}
} else {
map.set(option, level);
this.buildMapOfCategories(map, subCategories, level + 1);
}
}
});
}
public render() { public render() {
const { placeholder, label, options, t, names, terms, valueField, labelField, byKey, classes } = this.props; const { placeholder, label, options, indented, t, names, terms, valueField, labelField, classes } = this.props;
const { input } = _.get(this.props, names[0]); const { input } = _.get(this.props, names[0]);
const { input: notInput } = _.get(this.props, names[1]); const { input: notInput } = _.get(this.props, names[1]);
const { text, values, notValues } = this.state; const { text, values, notValues } = this.state;
const withOptions: boolean = options !== undefined && typeof options === 'object'; const withOptions: boolean = options !== undefined && typeof options === 'object';
const fieldOptions = options && (byKey ? Object.keys(options).map((val) => +val) : options); const filteredOptions = this.getFilteredOptions();
const filteredOptions = terms ? (fieldOptions && fieldOptions.filter((opt) => terms.get(valueField ? opt[valueField] : `${opt}`) > 0)) : fieldOptions;
let tree;
if (indented && options) {
tree = new Map<string, number>();
this.buildMapOfCategories(tree, Object.keys(options), 0);
}
return ( return (
<div> <div>
{ withOptions ? { withOptions ?
(!filteredOptions || filteredOptions.length === 0 ? (!filteredOptions || filteredOptions.length === 0 ?
(<div className="pl-15">{ t('common:f.noFilters') }</div>) (<div className="pl-15">{ t('common:f.noFilters') }</div>)
: : <FormControl fullWidth component={ 'fieldset' as 'div' } className="full-width">
(<FieldArray { label && <FormLabel component="label">{ label }</FormLabel> }
formLabel={ label } <FormGroup>
name={ names[0] } { filteredOptions.map((key) => (
singleColumn <FormControlLabel
component={ CheckboxSelection } key={ `${ key }` } style={ { paddingLeft: `${tree ? (tree.get(key) * 20) : 0}px`, marginRight: 0 } }
valueField={ valueField } classes={ {label: 'full-width', root: 'mr-0'} }
options={ filteredOptions } label={
classes={ {label: 'full-width'} } <span className="full-width" style={ {display: 'flex', justifyContent: 'space-between'} }>
renderOptionLabel={ (opt) => { `${ t(options[key]) || (labelField ? key[labelField] : key) }` }
<span className="full-width" style={ {display: 'flex', justifyContent: 'space-between'} }> { terms &&
{ `${t(options[opt]) || (labelField ? opt[labelField] : opt)}` } <span className="float-right">
{ terms && <Number value={ terms.get(valueField ? `${ key[valueField] }` : `${ key }`) }/>
<span className="float-right"> </span>
<Number value={ terms.get(valueField ? `${opt[valueField]}` : `${opt}`) }/> }
</span> </span>
} }
</span> control={
} <Checkbox
/>) value={ valueField ? `${ key[valueField] }` : key }
) checked={ values.indexOf(valueField ? `${ key[valueField] }` : key) >= 0 }
: onChange={ this.handleCheckbox }
/>
}
/>
)) }
</FormGroup>
</FormControl>
) :
<FormControl fullWidth className="full-width"> <FormControl fullWidth className="full-width">
<InputLabel>{ t(`${label}`) }</InputLabel> <InputLabel>{ t(`${label}`) }</InputLabel>
<Input <Input
...@@ -320,6 +405,7 @@ interface IStringArrFilter extends React.ClassAttributes<any> { ...@@ -320,6 +405,7 @@ interface IStringArrFilter extends React.ClassAttributes<any> {
labelField?: string; labelField?: string;
byKey?: boolean; byKey?: boolean;
classes: any; classes: any;
indented?: boolean;
t: any; t: any;
} }
...@@ -327,7 +413,7 @@ class StringArrFilter extends React.Component<IStringArrFilter, any> { ...@@ -327,7 +413,7 @@ class StringArrFilter extends React.Component<IStringArrFilter, any> {
public render() { public render() {
const { name, label, placeholder, options, terms, valueField, labelField, byKey, classes, t } = this.props; const { name, label, placeholder, options, indented, terms, valueField, labelField, byKey, classes, t } = this.props;
return ( return (
<div> <div>
<Fields <Fields
...@@ -341,6 +427,7 @@ class StringArrFilter extends React.Component<IStringArrFilter, any> { ...@@ -341,6 +427,7 @@ class StringArrFilter extends React.Component<IStringArrFilter, any> {
labelField={ labelField } labelField={ labelField }
byKey={ byKey } byKey={ byKey }
classes={ classes } classes={ classes }
indented={ indented }
t={ t } t={ t }
/> />
</div> </div>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment