Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
CelineK
kresus
Commits
469d82e1
Commit
469d82e1
authored
May 17, 2021
by
Benjamin Bouvier
Committed by
Nicolas Frandeboeuf
May 18, 2021
Browse files
Allow creating new categories from the bulk-edit category select (fixes #1114)
parent
3ab13e2e
Changes
3
Hide whitespace changes
Inline
Side-by-side
client/components/reports/bulkedit.tsx
View file @
469d82e1
...
...
@@ -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
();
...
...
client/components/reports/category-select.tsx
View file @
469d82e1
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
formatCreate
Category
Label
(
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
}
...
...
client/components/ui/fuzzy-or-native-select.tsx
View file @
469d82e1
...
...
@@ -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
.
c
reat
able
||
false
;
const
creatable
=
typeof
props
.
onC
reat
e
!==
'
undefined
'
;
const
clearable
=
!!
props
.
clearable
||
false
;
const
backspaceRemovesValue
=
!!
props
.
backspaceRemovesValue
||
false
;
const
required
=
!!
props
.
required
||
false
;
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment