Commit 0112f21f authored by Phyks (Lucas Verney)'s avatar Phyks (Lucas Verney)
Browse files

Use fuzzy selectors for category and types everywhere

Fix issue #601
parent 78b38e05
......@@ -3,44 +3,53 @@ import PropTypes from 'prop-types';
import { createSelector } from 'reselect';
import { connect } from 'react-redux';
import Select from 'react-select';
import { NONE_CATEGORY_ID } from '../../helpers';
import { get } from '../../store';
class CategorySelect extends React.Component {
handleChange = event => this.props.onChange(event.target.value);
handleChange = selectedValue => {
let value = NONE_CATEGORY_ID;
if (selectedValue) {
value = selectedValue.value;
}
if (value !== this.props.selectedValue) {
return this.props.onChange(value);
}
};
render() {
let style = this.props.borderColor
const style = this.props.borderColor
? { borderRight: `5px solid ${this.props.borderColor}` }
: null;
return (
<select
className="form-control btn-transparent"
<Select
value={this.props.selectedValue}
style={style}
id={this.props.id}
onChange={this.handleChange}>
{this.props.categories}
</select>
onChange={this.handleChange}
options={this.props.options}
/>
);
}
}
function categoryToOption(cat) {
return (
<option key={cat.id} value={cat.id}>
{cat.title}
</option>
);
}
const options = createSelector(
state => get.categories(state),
cats => {
// Put "No category" on top of the list.
let ops = [categoryToOption(cats.find(cat => cat.id === NONE_CATEGORY_ID))];
return ops.concat(
cats.filter(cat => cat.id !== NONE_CATEGORY_ID).map(cat => categoryToOption(cat))
let noneCategory = cats.find(cat => cat.id === NONE_CATEGORY_ID);
return [
{
value: noneCategory.id,
label: noneCategory.title
}
].concat(
cats
.filter(cat => cat.id !== NONE_CATEGORY_ID)
.map(cat => ({ value: cat.id, label: cat.title }))
);
}
);
......@@ -51,7 +60,7 @@ const Export = connect((state, props) => {
? null
: get.categoryById(state, props.selectedValue).color;
return {
categories: options(state),
options: options(state),
borderColor
};
})(CategorySelect);
......
......@@ -43,11 +43,11 @@ let fillShowDetails = (props, askDeleteConfirm) => {
<label className="col-xs-4 control-label">{$t('client.operations.amount')}</label>
<label className="col-xs-8">{props.formatCurrency(op.amount)}</label>
</div>
<div className="form-group clearfix">
<div className="form-group clearfix has-overflow">
<label className="col-xs-4 control-label">{$t('client.operations.type')}</label>
<div className="col-xs-8">{typeSelect}</div>
</div>
<div className="form-group clearfix">
<div className="form-group clearfix has-overflow">
<label className="col-xs-4 control-label">{$t('client.operations.category')}</label>
<div className="col-xs-8">{categorySelect}</div>
</div>
......
import React from 'react';
import PropTypes from 'prop-types';
import Select from 'react-select';
import moment from 'moment';
......@@ -12,52 +12,92 @@ import AmountInput from '../ui/amount-input';
import DatePicker from '../ui/date-picker';
import FoldablePanel from '../ui/foldable-panel';
const SearchCategorySelect = connect(
const ANY_TYPE_ID = '';
const SearchTypeSelect = connect(
state => {
return {
defaultValue: get.searchFields(state).categoryId,
categories: get.categories(state)
defaultValue: get.searchFields(state).type,
types: get.types(state)
};
},
dispatch => {
return {
handleChange(event) {
actions.setSearchField(dispatch, 'categoryId', event.target.value);
handleOperationType: selectedValue => {
let value = ANY_TYPE_ID;
if (selectedValue) {
value = selectedValue.value;
}
actions.setSearchField(dispatch, 'type', value);
}
};
}
)(props => {
let { defaultValue, categories, handleChange } = props;
let unknownType = props.types.find(type => type.name === UNKNOWN_OPERATION_TYPE);
let noneCategory = categories.find(cat => cat.id === NONE_CATEGORY_ID);
categories = categories.filter(cat => cat.id !== NONE_CATEGORY_ID);
// Types are not sorted.
let types = [unknownType].concat(
props.types.filter(type => type.name !== UNKNOWN_OPERATION_TYPE)
);
let options = [
<option key="_" value="">
{$t('client.search.any_category')}
</option>,
<option key={noneCategory.id} value={noneCategory.id}>
{noneCategory.title}
</option>
let typeOptions = [
{
value: ANY_TYPE_ID,
label: $t('client.search.any_type')
}
].concat(
categories.map(cat => (
<option key={cat.id} value={cat.id}>
{cat.title}
</option>
))
types.map(type => ({
value: type.name,
label: $t(`client.${type.name}`)
}))
);
return (
<select className="form-control" id={props.id} value={defaultValue} onChange={handleChange}>
{options}
</select>
<Select
onChange={props.handleOperationType}
options={typeOptions}
value={props.defaultValue}
/>
);
});
SearchCategorySelect.propTypes = {
// A string to link the input to a label for exemple.
id: PropTypes.string
};
const ANY_CATEGORY_ID = '';
const SearchCategorySelect = connect(
state => {
return {
defaultValue: get.searchFields(state).categoryId,
categories: get.categories(state)
};
},
dispatch => {
return {
handleChange(selectedValue) {
let value = ANY_CATEGORY_ID;
if (selectedValue) {
value = selectedValue.value;
}
actions.setSearchField(dispatch, 'categoryId', value);
}
};
}
)(props => {
let noneCategory = props.categories.find(cat => cat.id === NONE_CATEGORY_ID);
let categories = props.categories.filter(cat => cat.id !== NONE_CATEGORY_ID);
let options = [
{
value: ANY_CATEGORY_ID,
label: $t('client.search.any_category')
},
{
value: noneCategory.id,
label: noneCategory.title
}
].concat(categories.map(cat => ({ value: cat.id, label: cat.title })));
return <Select value={props.defaultValue} onChange={props.handleChange} options={options} />;
});
const MinDatePicker = connect(
(state, props) => {
......@@ -130,31 +170,9 @@ class SearchComponent extends React.Component {
}
render() {
let unknownType = this.props.types.find(type => type.name === UNKNOWN_OPERATION_TYPE);
// Types are not sorted.
let types = [unknownType].concat(
this.props.types.filter(type => type.name !== UNKNOWN_OPERATION_TYPE)
);
let typeOptions = [
<option key="_" value="">
{$t('client.search.any_type')}
</option>
].concat(
types.map(type => (
<option key={type.name} value={type.name}>
{$t(`client.${type.name}`)}
</option>
))
);
let handleKeyword = event => {
this.props.setKeywords(event.target.value);
};
let handleOperationType = event => {
this.props.setType(event.target.value);
};
let handleAmountLow = value => {
this.props.setAmountLow(Number.isNaN(value) ? null : value);
};
......@@ -206,12 +224,7 @@ class SearchComponent extends React.Component {
<label htmlFor="type-selector">{$t('client.search.type')}</label>
<select
className="form-control"
id="type-selector"
onChange={handleOperationType}>
{typeOptions}
</select>
<SearchTypeSelect id="type-selector" />
</div>
<div className="search-amounts">
......@@ -267,7 +280,6 @@ class SearchComponent extends React.Component {
const Export = connect(
state => {
return {
types: get.types(state),
displaySearchDetails: get.displaySearchDetails(state)
};
},
......@@ -283,10 +295,6 @@ const Export = connect(
actions.setSearchField(dispatch, 'keywords', keywords);
},
setType(type) {
actions.setSearchField(dispatch, 'type', type);
},
setAmountLow(amountLow) {
actions.setSearchField(dispatch, 'amountLow', amountLow);
},
......
......@@ -3,21 +3,31 @@ import PropTypes from 'prop-types';
import { createSelector } from 'reselect';
import { connect } from 'react-redux';
import Select from 'react-select';
import { translate as $t } from '../../helpers';
import { get } from '../../store';
class TypeSelect extends React.Component {
handleChange = event => this.props.onChange(event.target.value);
handleChange = selectedValue => {
if (
selectedValue &&
selectedValue.value &&
selectedValue.value !== this.props.selectedValue
) {
this.props.onChange(selectedValue.value);
}
};
render() {
return (
<select
className="form-control btn-transparent"
<Select
value={this.props.selectedValue}
id={this.props.id}
onChange={this.handleChange}>
{this.props.types}
</select>
clearable={false}
onChange={this.handleChange}
options={this.props.types}
/>
);
}
}
......@@ -25,11 +35,10 @@ class TypeSelect extends React.Component {
const options = createSelector(
state => get.types(state),
types => {
return types.map(type => (
<option key={`operation-type-select-operation-${type.id}`} value={type.name}>
{$t(`client.${type.name}`)}
</option>
));
return types.map(type => ({
value: type.name,
label: $t(`client.${type.name}`)
}));
}
);
......
......@@ -123,7 +123,7 @@ class AddOperationModal extends React.Component {
/>
</div>
<div className="form-group">
<div className="form-group has-overflow">
<label className="control-label" htmlFor={`type${this.props.account.id}`}>
{$t('client.addoperationmodal.type')}
</label>
......@@ -158,7 +158,7 @@ class AddOperationModal extends React.Component {
/>
</div>
<div className="form-group">
<div className="form-group has-overflow">
<label
className="control-label"
htmlFor={`category${this.props.account.id}`}>
......
......@@ -704,6 +704,12 @@ form.search p.search-buttons button:last-child {
}
}
.operation-panel td > .Select,
.modal-body .Select {
overflow: visible;
z-index: auto;
}
.input-group-addon {
color: #a2a2a2;
}
......
Supports Markdown
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