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

Allow creating new categories from the bulk-edit category select (fixes #1114)

parent 3ab13e2e
......@@ -8,6 +8,7 @@ import ClearableInput from '../ui/clearable-input';
import FuzzyOrNativeSelect from '../ui/fuzzy-or-native-select';
import DisplayIf, { IfNotMobile } from '../ui/display-if';
import { useGenericError } from '../../hooks';
import { formatCreateCategoryLabel, useOnCreateCategory } from './category-select';
const NO_TYPE_ID = null;
const NO_CAT = null;
......@@ -45,6 +46,8 @@ function categoryNotFoundMessage() {
// Have a resetable combo list to select a category.
const BulkEditCategorySelect = (props: { onChange: (categoryId: number | null) => void }) => {
const dispatch = useDispatch();
const categories = useKresusState(state => get.categories(state));
const options = useMemo(() => {
......@@ -60,22 +63,40 @@ const BulkEditCategorySelect = (props: { onChange: (categoryId: number | null) =
].concat(otherCategories.map(cat => ({ value: cat.id, label: cat.label })));
}, [categories]);
const [currentValue, setCurrentValue] = useState<number | null>(null);
const propsOnChange = props.onChange;
const onChange = useCallback(
(newVal: string | null) => {
propsOnChange(newVal === null ? null : parseInt(newVal, 10));
const newValInt = newVal === null ? null : parseInt(newVal, 10);
setCurrentValue(newValInt);
propsOnChange(newValInt);
},
[setCurrentValue, propsOnChange]
);
const updateOnCreate = useCallback(
(value: number | null) => {
propsOnChange(value);
setCurrentValue(value);
},
[propsOnChange]
[setCurrentValue, propsOnChange]
);
const onCreateCategory = useOnCreateCategory(dispatch, updateOnCreate);
const currentValueStr = currentValue === null ? NULL_OPTION : currentValue;
return (
<FuzzyOrNativeSelect
clearable={true}
noOptionsMessage={categoryNotFoundMessage}
onChange={onChange}
onCreate={onCreateCategory}
formatCreateLabel={formatCreateCategoryLabel}
options={options}
placeholder={$t('client.bulkedit.category_placeholder')}
value={NULL_OPTION}
value={currentValueStr}
/>
);
};
......@@ -97,6 +118,7 @@ const BulkEditComponent = (props: {
const [type, setType] = useState<string | null>(NO_TYPE_ID);
const [categoryId, setCategoryId] = useState<number | null>(NO_CAT);
const [customLabel, setCustomLabel] = useState(NO_LABEL);
const dispatch = useDispatch();
......
import React, { useCallback } from 'react';
import React, { Dispatch, useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { createSelector } from 'reselect';
......@@ -14,7 +14,7 @@ import { get, actions, GlobalState } from '../../store';
import FuzzyOrNativeSelect from '../ui/fuzzy-or-native-select';
function formatCreateLabel(label: string): string {
export function formatCreateCategoryLabel(label: string): string {
return $t('client.operations.create_category', { label });
}
......@@ -51,6 +51,27 @@ interface Props {
className?: string;
}
export const useOnCreateCategory = (
dispatch: Dispatch<any>,
propsOnChange: (value: number | null) => void
) => {
const onCreate = useCallback(
async (label: string) => {
try {
const category = await actions.createCategory(dispatch, {
label,
color: generateColor(),
});
propsOnChange(category.id);
} catch (err) {
notify.error($t('client.category.creation_error', { error: err.toString() }));
}
},
[dispatch, propsOnChange]
);
return onCreate;
};
const CategorySelector = (props: Props) => {
let className = 'form-element-block';
if (props.className) {
......@@ -78,20 +99,7 @@ const CategorySelector = (props: Props) => {
[propsOnChange]
);
const onCreate = useCallback(
async (label: string) => {
try {
const category = await actions.createCategory(dispatch, {
label,
color: generateColor(),
});
propsOnChange(category.id);
} catch (err) {
notify.error($t('client.category.creation_error', { error: err.toString() }));
}
},
[dispatch, propsOnChange]
);
const onCreate = useOnCreateCategory(dispatch, propsOnChange);
return (
<FuzzyOrNativeSelect
......@@ -99,8 +107,7 @@ const CategorySelector = (props: Props) => {
value={props.value}
className={className}
clearable={false}
creatable={true}
formatCreateLabel={formatCreateLabel}
formatCreateLabel={formatCreateCategoryLabel}
options={options}
onChange={onChange}
onCreate={onCreate}
......
......@@ -23,9 +23,6 @@ export interface ComboboxProps {
// A boolean telling whether the fuzzy-select should allow to clear the input.
clearable?: boolean;
// A boolean telling if the fuzzy-select should allow to create an option.
creatable?: boolean;
// A function returning the text to display when no such options are found,
// in fuzzy mode.
noOptionsMessage?: () => string;
......@@ -33,8 +30,8 @@ export interface ComboboxProps {
// A callback to be called when the user selects a new value.
onChange: (newValue: string | null) => void;
// A callback to be called when a new value is created, for a creatable, in
// fuzzy mode.
// A callback to be called when a new value is created in fuzzy mode. If
// absent, indicates that it's not possible to create new entries.
onCreate?: (label: string) => void;
// An array of options in the select.
......@@ -66,7 +63,7 @@ const FuzzyOrNativeSelect = (props: ComboboxProps) => {
const isSearchable = !isSmallScreen && (!!props.isSearchable || true);
// Default values.
const creatable = !!props.creatable || false;
const creatable = typeof props.onCreate !== 'undefined';
const clearable = !!props.clearable || false;
const backspaceRemovesValue = !!props.backspaceRemovesValue || false;
const required = !!props.required || false;
......
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