casey-chow/tigertrade

View on GitHub
client/src/components/FilterBar.js

Summary

Maintainability
F
5 days
Test Coverage
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import {
  withRouter,
  propTypes as routerPropTypes,
} from 'react-router-dom';

import { isEmpty, omit } from 'lodash';

import DatePicker from 'material-ui-build/build/DatePicker';

import { grey300 } from 'material-ui/styles/colors';

import Paper from 'material-ui/Paper';
import FlatButton from 'material-ui/FlatButton';
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
import TextField from 'material-ui/TextField';
import Toggle from 'material-ui/Toggle';
import FavoriteIcon from 'material-ui/svg-icons/action/favorite';
import WatchIcon from 'material-ui/svg-icons/action/visibility';
import PhotoIcon from 'material-ui/svg-icons/image/photo';
import ExpandMoreIcon from 'material-ui/svg-icons/navigation/expand-more';
import ExpandLessIcon from 'material-ui/svg-icons/navigation/expand-less';

import { loadPosts } from '../actions/common';
import { setExpandAll, toggleFilterBar } from '../actions/ui';
import { loadWatches, postWatch } from '../actions/watches';
import { writeHistory } from '../helpers/query';

const mapStateToProps = state => ({
  displayMode: state.displayMode,
  expanded: state.filterBarExpanded,
  query: state.currentQuery,
  leftDrawerVisible: state.leftDrawerVisible,
  expandAll: state.expandAll,
});

@withRouter
@connect(mapStateToProps)
export default class FilterBar extends Component {
  static propTypes = {
    ...routerPropTypes,
    contentContainer: PropTypes.any.isRequired, // eslint-disable-line react/forbid-prop-types
    dispatch: PropTypes.func.isRequired,
    displayMode: PropTypes.string.isRequired,
    expandAll: PropTypes.bool.isRequired,
    expanded: PropTypes.bool.isRequired,
    query: PropTypes.shape({
      isStarred: PropTypes.bool,
      hasPhotos: PropTypes.bool,
      minPrice: PropTypes.bool,
      maxPrice: PropTypes.bool,
      minCreateDate: PropTypes.date,
      maxCreateDate: PropTypes.date,
      order: PropTypes.string,
      includeInactive: PropTypes.bool,
    }).isRequired,
    leftDrawerVisible: PropTypes.bool.isRequired,
    style: PropTypes.object, // eslint-disable-line react/forbid-prop-types
  }

  static defaultProps = {
    style: {
      display: 'flex',
      flexDirection: 'row-reverse',
      flexWrap: 'wrap',
      justifyContent: 'space-around',
      alignItems: 'center',
      alignContent: 'space-around',
    },
  };

  static styles = {
    base: {
      minHeight: '4rem',
      right: '0',
      paddingTop: '0.5em',
      paddingBottom: '0.5em',
      zIndex: '50',
    },
    priceField: {
      maxWidth: '6.5rem',
      width: '100%',
    },
  }

  state = {
    expanded: false,
    expandAll: false,
    includeInactive: false,
    order: 'creationDateDesc',
    isStarred: false,
    hasPhotos: false,
    minPrice: -1,
    maxPrice: -1,
    minCreateDate: undefined,
    maxCreateDate: undefined,
  }

  componentWillMount() {
    this.setState({
      expanded: this.props.expanded,
      expandAll: this.props.expandAll,
      includeInactive: this.props.query.includeInactive,
      order: this.props.query.order || 'creationDateDesc',
      isStarred: this.props.query.isStarred,
      hasPhotos: this.props.query.hasPhotos,
      minPrice: this.props.query.minPrice / 100 || '',
      maxPrice: this.props.query.maxPrice / 100 || '',
      minCreateDate: this.props.query.minCreateDate,
      maxCreateDate: this.props.query.maxCreateDate,
    });
  }

  componentWillReceiveProps(nextProps) {
    this.setState({
      expanded: nextProps.expanded,
      expandAll: nextProps.expandAll,
      includeInactive: nextProps.query.includeInactive,
      order: nextProps.query.order || 'creationDateDesc',
      isStarred: nextProps.query.isStarred,
      hasPhotos: nextProps.query.hasPhotos,
      minPrice: nextProps.query.minPrice / 100 || '',
      maxPrice: nextProps.query.maxPrice / 100 || '',
      minCreateDate: nextProps.query.minCreateDate,
      maxCreateDate: nextProps.query.maxCreateDate,
    });
  }

  handleFavorite = () => {
    const isStarred = !this.props.query.isStarred;
    const query = { isStarred };
    this.setState(query);
    this.props.dispatch(loadPosts(
      this.props.displayMode,
      { query },
    )).then(() => {
      writeHistory(this.props);
    });
  }

  handlePhoto = () => {
    const hasPhotos = !this.props.query.hasPhotos;
    const query = { hasPhotos };
    this.setState(query);
    this.props.dispatch(loadPosts(
      this.props.displayMode,
      { query },
    )).then(() => {
      writeHistory(this.props);
    });
  }

  handleExpandedToggle = () => {
    const expanded = !this.props.expanded;
    this.setState({ expanded });
    this.props.dispatch(toggleFilterBar());
  }

  handleExpandAllToggle = (event, checked) => {
    this.setState({ expandAll: checked });
    if (this.props.contentContainer) {
      this.props.contentContainer.scrollTop = 0;
    }
    this.props.dispatch(setExpandAll(checked));
  }

  handleIncludeInactiveToggle = (event, checked) => {
    const includeInactive = checked;
    const query = { includeInactive };
    this.setState(query);
    this.props.dispatch(loadPosts(
      this.props.displayMode,
      { query },
    )).then(() => {
      writeHistory(this.props);
    });
  }

  handleOrder = (event, index, order) => {
    this.setState({ order });
    if (this.props.contentContainer) {
      this.props.contentContainer.scrollTop = 0;
    }
    const query = { order: (order === 'creationDateDesc') ? undefined : order };
    this.props.dispatch(loadPosts(
      this.props.displayMode,
      { query },
    )).then(() => {
      writeHistory(this.props);
    });
  }

  handleMinChange = (event, minPrice) => {
    this.setState({ minPrice });
    const query = { minPrice: (minPrice === '') ? undefined : Math.floor(minPrice * 100) };
    this.props.dispatch(loadPosts(
      this.props.displayMode,
      { query },
    ));
  }

  handleMaxChange = (event, maxPrice) => {
    this.setState({ maxPrice });
    const query = { maxPrice: (maxPrice === '') ? undefined : Math.floor(maxPrice * 100) };
    this.props.dispatch(loadPosts(
      this.props.displayMode,
      { query },
    ));
  }

  handlePostedAfterChange = (event, minCreateDate) => {
    const query = { minCreateDate };
    this.setState(query);
    this.props.dispatch(loadPosts(
      this.props.displayMode,
      { query },
    )).then(() => {
      writeHistory(this.props);
    });
  }

  handlePostedBeforeChange = (event, maxCreateDate) => {
    const query = { maxCreateDate };
    this.setState(query);
    this.props.dispatch(loadPosts(
      this.props.displayMode,
      { query },
    )).then(() => {
      writeHistory(this.props);
    });
  }

  handleOnBlur = () => {
    writeHistory(this.props);
  }

  handleWatchButtonTap = () => {
    this.props.dispatch(postWatch(null, 'Successfully watched search'));
    this.props.dispatch(loadWatches());
    this.props.history.push('/watches');
  }

  render() {
    const { query, leftDrawerVisible, location } = this.props;
    const styles = FilterBar.styles;
    const isListing = location.pathname.startsWith('/listings');
    const isSeek = location.pathname.startsWith('/seeks');

    return (
      <div>
        { (isListing || isSeek) &&
          <Paper
            style={{
              ...styles.base,
              left: leftDrawerVisible ? '20vw' : '0',
              ...this.props.style,
            }}
          >
            { isListing &&
              <FlatButton
                primary
                icon={this.state.expanded ? <ExpandLessIcon /> : <ExpandMoreIcon />}
                labelPosition="before"
                label={this.state.expanded ? 'Hide Filters' : 'Show Filters'}
                onTouchTap={this.handleExpandedToggle}
              />
            }
            <div>
              <Toggle
                label="Expand All"
                labelPosition="right"
                toggled={this.state.expandAll}
                onToggle={this.handleExpandAllToggle}
              />
            </div>
            { isSeek &&
              <div>
                <Toggle
                  label="Include Bought"
                  labelPosition="right"
                  toggled={this.state.includeInactive}
                  onToggle={this.handleIncludeInactiveToggle}
                />
              </div>
            }
            { isListing &&
              <SelectField
                value={this.state.order}
                onChange={this.handleOrder}
                autoWidth
              >
                <MenuItem value={'creationDateDesc'} primaryText="Most Recently Created" />
                <MenuItem value={'creationDateAsc'} primaryText="Oldest" />
                <MenuItem value={'priceAsc'} primaryText="Cheapest" />
                <MenuItem value={'priceDesc'} primaryText="Most Expensive" />
                <MenuItem value={'expirationDateAsc'} primaryText="Soonest Expiration" />
                <MenuItem value={'expirationDateDesc'} primaryText="Furthest Expiration" />
              </SelectField>
            }
            { isListing &&
              <FlatButton
                secondary
                icon={<FavoriteIcon />}
                label="Favorites Only"
                backgroundColor={this.state.isStarred ? grey300 : 'transparent'}
                onTouchTap={this.handleFavorite}
              />
            }
            { (isListing && !location.pathname.startsWith('/listings/mine')) &&
              <FlatButton
                primary
                icon={<WatchIcon />}
                label="Watch this Search"
                onTouchTap={this.handleWatchButtonTap}
                disabled={isEmpty(omit(query, ['isStarred', 'limit', 'hasPhotos', 'order', 'includeInactive']))}
              />
            }
          </Paper>
        }
        { this.state.expanded && isListing &&
          <Paper
            style={{
              ...styles.base,
              left: leftDrawerVisible ? '20vw' : '0',
              ...this.props.style,
            }}
          >
            <div>
              <Toggle
                label="Include Sold"
                labelPosition="right"
                toggled={this.state.includeInactive}
                onToggle={this.handleIncludeInactiveToggle}
              />
            </div>
            <FlatButton
              secondary
              icon={<PhotoIcon />}
              label="Has Photos Only"
              backgroundColor={this.state.hasPhotos ? grey300 : 'transparent'}
              onTouchTap={this.handlePhoto}
            />
            <TextField
              hintText="Max Price"
              type="number"
              onChange={this.handleMaxChange}
              onBlur={this.handleOnBlur}
              value={this.state.maxPrice}
              style={styles.priceField}
              prefix="$"
              min="0"
              step="1"
            />
            <TextField
              hintText="Min Price"
              type="number"
              onChange={this.handleMinChange}
              onBlur={this.handleOnBlur}
              value={this.state.minPrice}
              style={styles.priceField}
              prefix="$"
              min="0"
              step="1"
            />
            <DatePicker
              hintText="Posted before"
              clearSelection
              value={this.state.maxCreateDate}
              onChange={this.handlePostedBeforeChange}
              style={styles.priceField}
              textFieldStyle={styles.priceField}
            />
            <DatePicker
              hintText="Posted after"
              clearSelection
              value={this.state.minCreateDate}
              onChange={this.handlePostedAfterChange}
              style={styles.priceField}
              textFieldStyle={styles.priceField}
            />
          </Paper>
        }
      </div>
    );
  }
}