From 23e07b6876f6edbadc886db7c409094555cfb1fd Mon Sep 17 00:00:00 2001 From: Birk Johansson Date: Tue, 5 Mar 2024 17:23:24 +0100 Subject: [PATCH] feat(optionset) option search (#2688) * feat(optionset): add filtering for options * fix: use token as filter operator * fix: cleanup * fix: conflict * translation: add translation for disabled sort * fix: keep filter after adding or deleting --- .../AddOptionDialog.component.js | 2 +- .../option-set/OptionManagement.component.js | 50 +++++++++++++++---- .../OptionSorter/OptionSorter.component.js | 14 ++++-- src/EditModel/option-set/actions.js | 27 +++++++--- src/i18n/i18n_module_en.properties | 1 + 5 files changed, 72 insertions(+), 22 deletions(-) diff --git a/src/EditModel/option-set/OptionDialogForOptions/AddOptionDialog.component.js b/src/EditModel/option-set/OptionDialogForOptions/AddOptionDialog.component.js index 4e216f0fc..eeff8acaf 100644 --- a/src/EditModel/option-set/OptionDialogForOptions/AddOptionDialog.component.js +++ b/src/EditModel/option-set/OptionDialogForOptions/AddOptionDialog.component.js @@ -49,7 +49,7 @@ class AddOptionDialog extends Component { this.setState({ isSaving: false }); this.props.onRequestClose(); // After the save was successful we request the options from the server to get the updated list - actions.getOptionsFor(this.props.parentModel); + actions.getOptionsFor(this.props.parentModel, undefined); } onSaveError = ({ message, translate }) => { diff --git a/src/EditModel/option-set/OptionManagement.component.js b/src/EditModel/option-set/OptionManagement.component.js index 0403a746b..eb4db7861 100644 --- a/src/EditModel/option-set/OptionManagement.component.js +++ b/src/EditModel/option-set/OptionManagement.component.js @@ -9,9 +9,9 @@ import RaisedButton from 'material-ui/RaisedButton/RaisedButton'; import Pagination from 'd2-ui/lib/pagination/Pagination.component'; import LinearProgress from 'material-ui/LinearProgress/LinearProgress'; -import AlertIcon from 'material-ui/svg-icons/alert/warning'; import TranslationDialog from 'd2-ui/lib/i18n/TranslationDialog.component'; import SharingDialog from '@dhis2/d2-ui-sharing-dialog'; +import TextField from 'material-ui/TextField'; import OptionSorter from './OptionSorter/OptionSorter.component'; import OptionDialogForOptions from './OptionDialogForOptions/OptionDialogForOptions.component'; @@ -32,6 +32,12 @@ const styles = { dataTableWrap: { position: 'relative', }, + sortBarWrap: { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'baseline', + marginTop: '-16px', + }, sortBarStyle: { display: 'flex', justifyContent: 'flex-end', @@ -67,6 +73,7 @@ class OptionManagement extends Component { isSorting: false, modelToTranslate: null, modelToShare: null, + filter: '', }; this.i18n = context.d2.i18n; @@ -74,13 +81,13 @@ class OptionManagement extends Component { componentDidMount() { this.subscription = actions - .getOptionsFor(this.props.model) + .getOptionsFor(this.props.model, undefined) .subscribe(() => this.forceUpdate()); } componentWillReceiveProps(newProps) { if (this.props.model !== newProps.model) { - actions.getOptionsFor(newProps.model); + actions.getOptionsFor(newProps.model, this.state.filter); } } @@ -90,9 +97,16 @@ class OptionManagement extends Component { } } + handleFilter = ({ target: { value } }) => { + this.setState({ filter: value }) + actions.getOptionsFor(this.props.model, value); + }; + onAddOption = () => actions.setActiveModel(); - onAddDialogClose = () => actions.closeOptionDialog(); + onAddDialogClose = () => { + actions.closeOptionDialog(); + } onEditOption = model => actions.setActiveModel(model); @@ -165,22 +179,36 @@ class OptionManagement extends Component { modelToShare }) }, - delete: modelToDelete => actions.deleteOption(modelToDelete, this.props.model), + delete: modelToDelete => { + const deleteRef = actions.deleteOption(modelToDelete, this.props.model) + }, translate: (modelToTranslate) => { this.setState({ modelToTranslate, }); }, }; - + const isEmptyFilter = typeof this.state.filter === 'string' && this.state.filter.trim().length === 0 + return (
{this.renderPagination()} - +
+ + + +
{this.props.isLoading && } this.onSortBy('displayName')} - disabled={this.state.isSorting} + disabled={isDisabled} label={this.getTranslation(this.state.isSorting ? 'sorting' : 'sort_by_name')} + title={disabledLabel} /> this.onSortBy('code')} - disabled={this.state.isSorting} + disabled={isDisabled} label={this.getTranslation(this.state.isSorting ? 'sorting' : 'sort_by_code')} + title={disabledLabel} /> setSortDialogOpenTo(true)} - disabled={this.state.isSorting} + disabled={isDisabled} label={this.getTranslation(this.state.isSorting ? 'sorting' : 'sort_manually')} + title={disabledLabel} />
@@ -121,11 +127,13 @@ class OptionSorter extends Component { OptionSorter.propTypes = { buttonStyle: PropTypes.object, + disabled: PropTypes.bool, style: PropTypes.object, }; OptionSorter.defaultProps = { buttonStyle: {}, + disabled: false, style: {}, }; diff --git a/src/EditModel/option-set/actions.js b/src/EditModel/option-set/actions.js index 8b92f0d8b..ef2ede1aa 100644 --- a/src/EditModel/option-set/actions.js +++ b/src/EditModel/option-set/actions.js @@ -18,11 +18,17 @@ const actions = Action.createActionsFromNames([ 'updateModel', ], 'optionSet'); -export async function loadOptionsForOptionSet(optionSetId, paging) { +export async function loadOptionsForOptionSet(optionSetId, { paging, filter }) { const d2 = await getInstance(); - return d2.models.option + let filteredOptions = d2.models.option .filter().on('optionSet.id').equals(optionSetId) + + if(filter) { + filteredOptions = filteredOptions.filter().on('identifiable').token(filter) + } + + return filteredOptions .list({ fields: ':all,attributeValues[:owner,value,attribute[id,name,displayName]]', paging, order: 'sortOrder:asc' }); } @@ -40,6 +46,7 @@ function processResponse(options) { onePage: true, isLoading: false, options: optionsInOrder, + filter: optionsForOptionSetStore.state.filter }); }); } @@ -54,6 +61,7 @@ function processResponse(options) { options.pager.getPreviousPage() .then(processResponse); }, + filter: optionsForOptionSetStore.state.filter, pager: options.pager, onePage: false, isLoading: false, @@ -138,14 +146,19 @@ actions.saveOption }); actions.getOptionsFor - .subscribe(async ({ data: model, complete }) => { + .distinctUntilChanged() + .debounceTime(250) + .subscribe(async ({ data: [ model, newFilter ], complete }) => { + const filter = newFilter == undefined ? optionsForOptionSetStore.state.filter : newFilter; + optionsForOptionSetStore.setState({ + ...optionsForOptionSetStore.state, isLoading: true, - options: [], - }); + filter + }) if (model && model.id) { - loadOptionsForOptionSet(model.id, true) + loadOptionsForOptionSet(model.id, { paging: true, filter}) .then(processResponse) .then(() => complete()); } @@ -173,7 +186,7 @@ actions.deleteOption return api.delete(`${modelParent.modelDefinition.apiEndpoint}/${modelParent.id}/options/${modelToDelete.id}`) .then(() => modelToDelete.delete()) .then(() => snackActions.show({ message: deleteMessage })) - .then(() => actions.getOptionsFor(modelParent)) + .then(() => actions.getOptionsFor(modelParent, undefined)) .then(() => modelParent.options.delete(modelToDelete.id)) .then(complete) .catch(error); diff --git a/src/i18n/i18n_module_en.properties b/src/i18n/i18n_module_en.properties index d5f192d08..265a1ef97 100644 --- a/src/i18n/i18n_module_en.properties +++ b/src/i18n/i18n_module_en.properties @@ -2343,3 +2343,4 @@ org_unit_label=Custom label for registering unit relationship_label=Custom label for relationship note_label=Custom label for note tracked_entity_attribute_label=Custom label for tracked entity attribute +sorting_is_disabled_when_filter_is_applied=Sorting is diabled when a filter is applied