Commit f32113e1 authored by Nicolas Frandeboeuf's avatar Nicolas Frandeboeuf Committed by Benjamin Bouvier
Browse files

Allow several categories in search (fix #319)

parent 7dc83952
......@@ -343,7 +343,7 @@ const Export = connect(
actions.setSearchFields(dispatch, {
dateLow: +fromDate,
dateHigh: +toDate,
categoryId
categoryIds: [categoryId]
});
},
......
......@@ -198,8 +198,8 @@ function filter(state, operationsIds, search) {
let filtered = operationsIds.map(id => get.operationById(state, id));
// Filter! Apply most discriminatory / easiest filters first
filtered = filterIf(search.categoryId !== '', filtered, op => {
return op.categoryId === search.categoryId;
filtered = filterIf(search.categoryIds.length > 0, filtered, op => {
return search.categoryIds.includes(op.categoryId);
});
filtered = filterIf(search.type !== '', filtered, op => {
......
......@@ -55,7 +55,6 @@ const SearchTypeSelect = connect(
return (
<FuzzyOrNativeSelect
className="form-element-block"
clearable={true}
noOptionsMessage={typeNotFoundMessage}
onChange={props.handleOperationType}
......@@ -65,8 +64,6 @@ const SearchTypeSelect = connect(
);
});
const ANY_CATEGORY_ID = '';
function categoryNotFoundMessage() {
return $t('client.operations.no_category_found');
}
......@@ -74,15 +71,15 @@ function categoryNotFoundMessage() {
const SearchCategorySelect = connect(
state => {
return {
defaultValue: get.searchFields(state).categoryId,
defaultValue: get.searchFields(state).categoryIds,
categories: get.categories(state)
};
},
dispatch => {
return {
handleChange(selectedValue) {
let value = selectedValue !== null ? selectedValue : ANY_CATEGORY_ID;
actions.setSearchField(dispatch, 'categoryId', value);
let value = selectedValue instanceof Array ? selectedValue : [];
actions.setSearchField(dispatch, 'categoryIds', value);
}
};
}
......@@ -91,10 +88,6 @@ const SearchCategorySelect = connect(
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
......@@ -103,12 +96,13 @@ const SearchCategorySelect = connect(
return (
<FuzzyOrNativeSelect
className="form-element-block"
clearable={true}
noOptionsMessage={categoryNotFoundMessage}
onChange={props.handleChange}
options={options}
value={props.defaultValue}
isMulti={true}
placeholder={$t('client.search.category_placeholder')}
/>
);
});
......
......@@ -14,9 +14,9 @@ const REACT_SELECT_FILTER = createFilter({
stringify: ({ label }) => label.toString()
});
const FuzzyOrNativeSelect = connect(state => {
const FuzzyOrNativeSelect = connect((state, props) => {
return {
useNativeSelect: get.isSmallScreen(state)
useNativeSelect: get.isSmallScreen(state) && !props.isMulti
};
})(
class Export extends React.Component {
......@@ -32,13 +32,25 @@ const FuzzyOrNativeSelect = connect(state => {
// That's the default case of react-select, when a value is
// selected.
value = event.value;
} else if (event instanceof Array && this.props.isMulti) {
// react-select with multiple values.
value = event.map(e => e.value);
} else {
// No values are selected.
assert(event === null || (event instanceof Array && event.length === 0));
value = null;
}
if (value !== this.props.value) {
let valueHasChanged = false;
if (this.props.isMulti) {
valueHasChanged =
value.length !== this.props.value.length ||
!value.every(v => this.props.value.includes(v));
} else {
valueHasChanged = value !== this.props.value;
}
if (valueHasChanged) {
this.props.onChange(value);
}
};
......@@ -87,7 +99,12 @@ const FuzzyOrNativeSelect = connect(state => {
className += value ? ' valid-fuzzy' : ' invalid-fuzzy';
}
let defaultOption = options.find(opt => opt.value === value);
let defaultOption;
if (this.props.isMulti) {
defaultOption = options.filter(opt => value.includes(opt.value));
} else {
defaultOption = options.find(opt => opt.value === value);
}
return (
<FuzzySelect
......@@ -103,6 +120,7 @@ const FuzzyOrNativeSelect = connect(state => {
options={options}
placeholder={placeholder}
value={defaultOption}
isMulti={this.props.isMulti}
/>
);
}
......@@ -145,7 +163,10 @@ FuzzyOrNativeSelect.propTypes = {
required: PropTypes.bool.isRequired,
// The value that's selected at start.
value: PropTypes.string.isRequired
value: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]).isRequired,
// A boolean telling whether the select allows several values.
isMulti: PropTypes.bool.isRequired
};
FuzzyOrNativeSelect.defaultProps = {
......@@ -153,7 +174,8 @@ FuzzyOrNativeSelect.defaultProps = {
clearable: false,
backspaceRemovesValue: true,
required: false,
className: ''
className: '',
isMulti: false
};
export default FuzzyOrNativeSelect;
......@@ -302,7 +302,7 @@ export const reducer = createReducerFromMap(uiState, reducers);
function initialSearch() {
return {
keywords: [],
categoryId: '',
categoryIds: [],
type: '',
amountLow: null,
amountHigh: null,
......@@ -340,7 +340,7 @@ export function hasSearchFields(state) {
let { search } = state;
return (
search.keywords.length ||
search.categoryId !== '' ||
search.categoryIds.length > 0 ||
search.type !== '' ||
search.amountLow !== null ||
search.amountHigh !== null ||
......
......@@ -225,6 +225,7 @@
"any_type": "Any type",
"keywords": "Keywords:",
"category": "Category:",
"category_placeholder": "Categories",
"type": "Type:",
"clear": "Clear",
"clearAndClose": "Clear and close",
......
......@@ -225,6 +225,7 @@
"any_type": "N'importe quel type",
"keywords": "Mots-clés :",
"category": "Catégorie :",
"category_placeholder": "Catégories",
"type": "Type :",
"clear": "Vider",
"clearAndClose": "Vider & fermer",
......
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