PrettyFilters.tsx 5.71 KB
Newer Older
Maxym Borodenko's avatar
Maxym Borodenko committed
1
import * as React from 'react';
2
import { bindActionCreators } from 'redux';
Maxym Borodenko's avatar
Maxym Borodenko committed
3
import { connect } from 'react-redux';
4
import { withStyles } from '@material-ui/core/styles';
Maxym Borodenko's avatar
Maxym Borodenko committed
5
import { translate } from 'react-i18next';
6
7
import { showSnackbar } from 'actions/snackbar';

Matija Obreza's avatar
Matija Obreza committed
8
9
import Chip from '@material-ui/core/Chip';
import Paper from '@material-ui/core/Paper';
Maxym Borodenko's avatar
Maxym Borodenko committed
10
11
import * as flattenjs from 'flattenjs';
import * as _ from 'lodash';
Matija Obreza's avatar
Subsets    
Matija Obreza committed
12
import { cleanFilters } from 'utilities';
13
import Accession from 'model/accession/Accession';
Maxym Borodenko's avatar
Maxym Borodenko committed
14
import {User} from 'model/user/User';
15
import PrettyDate from 'ui/common/time/PrettyDate';
Maxym Borodenko's avatar
Maxym Borodenko committed
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

/**
 * filterObj example: {crop: ["bannana", "apple"], description: {contains: "de"}, title: {contains: "ti"}}
 * If we want to replace the code with a title for showing, we need lookups object.
 * It contains pairs key - value:
 *      where key - should be equal to filterObj key which values we want to substitute.
 *      where value - object of codes and titles we want to show.
 * lookups example: {
 *  crop: {
 *      banana: 'Banana',
 *      apple: 'Apple',
 *      ...
 *    },
 *    ...
 *  }
 */
interface IPrettyFiltersProps extends React.ClassAttributes<any> {
33
34
35
36
37
38
39
  classes: any;
  onSubmit: (newFilter) => any;
  showSnackbar: (snack: string) => void;
  filterObj: object;
  lookups: object;
  prefix: string;
  t: any;
Maxym Borodenko's avatar
Maxym Borodenko committed
40
41
42
}

const styles = (theme) => ({
43
44
45
46
47
48
49
50
51
  root: {
    display: 'flex',
    justifyContent: 'center' as 'center',
    flexWrap: 'wrap' as 'wrap',
    padding: theme.spacing.unit / 2,
  },
  chip: {
    margin: theme.spacing.unit / 2,
  },
Maxym Borodenko's avatar
Maxym Borodenko committed
52
53
54
});

// Following keywords are skipped for showing
55
const keywordsToSkip = ['eq', 'contains', 'sw'];
Maxym Borodenko's avatar
Maxym Borodenko committed
56
57
58
59
60
61
62
63

/**
 * Remove value by path from filterObj and return updated filterObj.
 * @param filterObj - IPrettyFiltersProps.filterObj.
 * @param path - path to value to remove.
 * @return IPrettyFiltersProps.filterObj.
 */
function handleFilterObj(filterObj, path) {
64
65
66
67
68
69
70
  // clone original filter in order to not mutate it
  const clone = _.cloneDeep(filterObj);
  // array element
  if (path.endsWith(']')) {
    const lastIndex = path.lastIndexOf('[');
    const arrayPath = path.substring(0, lastIndex);
    const index = parseInt(path.substring(lastIndex).replace(/[\[\]']+/g, ''), 10);
Maxym Borodenko's avatar
Maxym Borodenko committed
71

72
    _.get(clone, arrayPath).splice(index, 1);
Maxym Borodenko's avatar
Maxym Borodenko committed
73

74
75
    return clone;
  }
Maxym Borodenko's avatar
Maxym Borodenko committed
76

77
  return _.omit(clone, [path]);
Maxym Borodenko's avatar
Maxym Borodenko committed
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
}

/**
 * Return pretty name.
 * For example we have path crop[0] and value banana, will return 'crop Banana'.
 * Another examples:
 *       'description.contains': 'de' -> 'description de'
 *       'title.eq': 'ti' -> 'title ti'
 * @param path - path to filterObj value ie crop[0]
 * @param value - filterObj value ie BN27.
 * @param lookups - IPrettyFiltersProps.filterObj.
 * @param prefix - for determination which page filter localize
 * @param t - i18n translation function
 * @return IPrettyFiltersProps.filterObj.
 */
function getLabelName(path, value, lookups, prefix, t) {
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
  const lastKey = path.replace(/\[(.+?)\]/g, '').split('.').pop();

  let name = value;
  if (lookups && lookups[lastKey]) {
    name = lookups[lastKey][value] || value;
  }

  const prettyPath: string = path
    // remove array indexes square brackets [0], [1]... from path.
    .replace(/\[(.+?)\]/g, '')
    // split path title.eq -> ['title', 'eq']
    .split('.')
    // skip for showing keywords such as 'contains', 'eq'...
    .filter((e) => keywordsToSkip.indexOf(e) === -1)
    // join
    .join('.');

  if (typeof name === 'boolean') {
    if (name) {
      return t(`f.${prefix}.${prettyPath}`);
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
114
    }
115
116
    return t('f.NOT', { what: t(`f.${prefix}.${prettyPath.replace(/^NOT\./, '')}`) });
  }
Viacheslav Pavlov's avatar
Viacheslav Pavlov committed
117

118
119
120
121
  const translatedPrettyPath = prettyPath.startsWith('NOT') ?
    t('f.NOT', { what: t(`f.${prefix}.${prettyPath.replace(/^NOT\./, '')}`) })
    :
    t(`f.${prefix}.${prettyPath}`);
122

123
  const translatedPrettyName = t(`${name}`);
Maxym Borodenko's avatar
Maxym Borodenko committed
124

125

126
127
128
  if (prettyPath.includes('Date')) {
    return <span>{ translatedPrettyPath }: <PrettyDate value={ new Date(name) } /></span>;
  }
129

130
  return `${translatedPrettyPath}: ${translatedPrettyName}`;
Maxym Borodenko's avatar
Maxym Borodenko committed
131
132
133
134
}

class PrettyFilters extends React.Component<IPrettyFiltersProps, any> {

135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
  constructor(props: IPrettyFiltersProps, context: any) {
    super(props, context);
    this.state = {
      chipData: flattenjs.convert(cleanFilters(props.filterObj)),
    };
  }

  protected handleDelete = (path) => () => {
    const { filterObj, onSubmit, showSnackbar } = this.props;
    const updated = handleFilterObj(filterObj, path);
    showSnackbar('Applying filters...');
    onSubmit(updated);
  }

  public componentWillReceiveProps(nextProps) {
    const chipData = flattenjs.convert(cleanFilters(nextProps.filterObj));
    if (!_.isEqual(chipData, this.state.chipData)) {
      this.setState({ chipData });
Maxym Borodenko's avatar
Maxym Borodenko committed
153
    }
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
  }

  public render() {
    const { classes, lookups, prefix, t } = this.props;
    const { chipData } = this.state;
    const dataArr = Object.getOwnPropertyNames(chipData);

    return dataArr.length > 0 && (
      <Paper square className={ classes.root }>
        { dataArr.map((key) => {
          return (
            <Chip
              key={ key }
              label={ getLabelName(key, chipData[key], lookups, prefix, t) }
              onDelete={ this.handleDelete(key) }
              className={ classes.chip }
            />
          );
        }) }
      </Paper>
    );
  }
Maxym Borodenko's avatar
Maxym Borodenko committed
176
177
178
179
180
}

const styled = withStyles(styles)(PrettyFilters);

const mapStateToProps = (state, ownProps) => ({
181
182
183
184
185
  lookups: {
    crop: [],
    category: '',
    sampStat: Accession.SAMPSTAT,
    storage: Accession.STORAGE,
Maxym Borodenko's avatar
Maxym Borodenko committed
186
    role: User.USERROLES,
187
188
189
    accessions: {
      true: 'Yes',
      false: 'No',
Maxym Borodenko's avatar
Maxym Borodenko committed
190
    },
191
  },
Maxym Borodenko's avatar
Maxym Borodenko committed
192
193
});

194
195
196
197
198
const mapDispatchToProps = (dispatch) => bindActionCreators({
  showSnackbar,
}, dispatch);

export default translate()(connect(mapStateToProps, mapDispatchToProps)(styled));