Home Reference Source

src/components/widgets/WidgetFilters.jsx

import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { getFormValues, reset } from 'redux-form';
import { isEqual, difference, map, isEmpty, unset, debounce } from 'lodash';
import { createStructuredSelector } from 'reselect';

import ReduxForm from './Form/ReduxForm';
import Filter from '../snippets/Filter/Filter';
import { dataRequestWidget } from '../../actions/widgets';
import { PREFIXES } from '../../constants/models';
import { setModel, removeModel } from '../../actions/models';
import { flatFields } from './Form/utils';
import { makeGetFilterModelSelector } from '../../selectors/models';
import { makeWidgetFilterVisibilitySelector } from '../../selectors/widgets';
import { validateField } from '../../core/validation/createValidator';

function generateFormName(props) {
  return `${props.widgetId}_filter`;
}

/**
 * Компонент WidgetFilters
 * @reactProps {string} widgetId
 * @reactProps {array} fieldsets
 * @reactProps {boolean} visible
 * @reactProps {boolean} hideButtons
 * @reactProps {array} blackResetList
 * @reactProps {object} filterModel
 * @reactProps {function} fetchWidget
 * @reactProps {function} clearFilterModel
 * @reactProps {function} setFilterModel
 * @reactProps {function} reduxFormFilter
 */
class WidgetFilters extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      defaultValues: props.filterModel,
    };
    this.formName = generateFormName(props);
    this.values = {};
    this.handleChangeModel = this.handleChangeModel.bind(this);
    this.handleFilter = this.handleFilter.bind(this);
    this.handleReset = this.handleReset.bind(this);
    this.validateAndFetch = this.validateAndFetch.bind(this);
    this.debouncedHandleFilter = debounce(this.handleFilter, 1000);
  }

  getChildContext() {
    return {
      _widgetFilter: {
        formName: this.formName,
        changeModel: this.handleChangeModel,
        filter: this.handleFilter,
        reset: this.handleReset,
      },
    };
  }

  componentWillUnmount() {
    const { widgetId, clearFilterModel } = this.props;
    clearFilterModel(widgetId);
  }

  componentDidUpdate(prevProps, prevState) {
    const { filterModel, reduxFormFilter } = this.props;
    const { defaultValues } = this.state;
    if (
      !isEqual(prevProps.filterModel, filterModel) &&
      !isEqual(filterModel, defaultValues) &&
      !isEqual(filterModel, reduxFormFilter)
    ) {
      this.setState(() => ({
        defaultValues: filterModel,
      }));
    }
  }

  handleChangeModel(values) {
    const {
      widgetId,
      filterModel,
      setFilterModel,
      searchOnChange,
    } = this.props;

    if (!isEqual(filterModel, values)) {
      this.values = { ...values };
      setFilterModel(widgetId, values);
      if (searchOnChange) {
        this.debouncedHandleFilter();
      }
    }
  }

  handleFilter() {
    this.validateAndFetch({ page: 1 });
  }

  handleReset() {
    const {
      fieldsets,
      blackResetList,
      widgetId,
      reduxFormFilter,
      setFilterModel,
      resetFilterModel,
    } = this.props;
    let newReduxForm = { ...reduxFormFilter };
    const toReset = difference(
      map(flatFields(fieldsets, []), 'id'),
      blackResetList
    );
    toReset.forEach(field => {
      unset(newReduxForm, field);
    });
    this.setState(
      {
        defaultValues: newReduxForm,
      },
      () => {
        resetFilterModel(this.formName);
        setFilterModel(widgetId, newReduxForm);
        this.validateAndFetch();
      }
    );
  }

  validateAndFetch(options) {
    const { widgetId, fetchWidget, validation, dispatch } = this.props;
    const { store } = this.context;
    validateField(validation, this.formName, store.getState(), true)(
      this.values,
      dispatch
    ).then(hasError => {
      if (!hasError) {
        fetchWidget(widgetId, options);
      }
    });
  }

  render() {
    const { fieldsets, visible, hideButtons, validation } = this.props;
    const { defaultValues } = this.state;
    return (
      <Filter
        style={{ display: !visible ? 'none' : '' }}
        hideButtons={hideButtons}
        onSearch={this.handleFilter}
        onReset={this.handleReset}
      >
        <ReduxForm
          form={this.formName}
          fieldsets={fieldsets}
          onChange={this.handleChangeModel}
          initialValues={defaultValues}
          validation={validation}
        />
      </Filter>
    );
  }
}

WidgetFilters.propTypes = {
  widgetId: PropTypes.string,
  fieldsets: PropTypes.array,
  visible: PropTypes.bool,
  blackResetList: PropTypes.array,
  filterModel: PropTypes.object,
  validation: PropTypes.object,
  clearFilterModel: PropTypes.func,
  setFilterModel: PropTypes.func,
  reduxFormFilter: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
  fetchWidget: PropTypes.func,
  hideButtons: PropTypes.bool,
  searchOnChange: PropTypes.bool,
};

WidgetFilters.defaultProps = {
  hideButtons: false,
  searchOnChange: false,
};

WidgetFilters.contextTypes = {
  store: PropTypes.object,
};

WidgetFilters.childContextTypes = {
  _widgetFilter: PropTypes.object.isRequired,
};

const mapStateToProps = createStructuredSelector({
  filterModel: (state, props) =>
    makeGetFilterModelSelector(props.widgetId)(state, props),
  visible: (state, props) =>
    makeWidgetFilterVisibilitySelector(props.widgetId)(state, props),
  reduxFormFilter: (state, props) =>
    getFormValues(generateFormName(props))(state) || {},
});

const mapDispatchToProps = dispatch => {
  return {
    dispatch,
    setFilterModel: (widgetId, data) =>
      dispatch(setModel(PREFIXES.filter, widgetId, data)),
    fetchWidget: (widgetId, options) =>
      dispatch(dataRequestWidget(widgetId, options)),
    clearFilterModel: widgetId =>
      dispatch(removeModel(PREFIXES.filter, widgetId)),
    resetFilterModel: formName => dispatch(reset(formName)),
  };
};

WidgetFilters = connect(
  mapStateToProps,
  mapDispatchToProps
)(WidgetFilters);

export default WidgetFilters;