import { filter, debounce, find } from 'lodash'; import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import Modal from 'antd/lib/modal'; import Input from 'antd/lib/input'; import List from 'antd/lib/list'; import { wrap as wrapDialog, DialogPropType } from '@/components/DialogWrapper'; import { BigMessage } from '@/components/BigMessage'; import LoadingState from '@/components/items-list/components/LoadingState'; import { toastr } from '@/services/ng'; class SelectItemsDialog extends React.Component { static propTypes = { dialog: DialogPropType.isRequired, dialogTitle: PropTypes.string, inputPlaceholder: PropTypes.string, selectedItemsTitle: PropTypes.string, searchItems: PropTypes.func.isRequired, // (searchTerm: string): Promise if `searchTerm === ''` load all itemKey: PropTypes.func, // (item) => string|number - return key of item (by default `id`) // left list // (item, { isSelected }) => { // content: node, // item contents // className: string = '', // additional class for item wrapper // isDisabled: bool = false, // is item clickable or disabled // } renderItem: PropTypes.func, // right list; args/results save as for `renderItem`. if not specified - `renderItem` will be used renderStagedItem: PropTypes.func, save: PropTypes.func, // (selectedItems[]) => Promise }; static defaultProps = { dialogTitle: 'Add Items', inputPlaceholder: 'Search...', selectedItemsTitle: 'Selected items', itemKey: item => item.id, renderItem: () => '', renderStagedItem: null, // use `renderItem` by default save: items => items, }; state = { searchTerm: '', loading: false, items: [], selected: [], saveInProgress: false, }; // eslint-disable-next-line react/sort-comp loadItems = (searchTerm = '') => { this.setState({ searchTerm, loading: true }, () => { this.props.searchItems(searchTerm) .then((items) => { // If another search appeared while loading data - just reject this set if (this.state.searchTerm === searchTerm) { this.setState({ items, loading: false }); } }) .catch(() => { if (this.state.searchTerm === searchTerm) { this.setState({ items: [], loading: false }); } }); }); }; search = debounce(this.loadItems, 200); componentDidMount() { this.loadItems(); } isSelected(item) { const key = this.props.itemKey(item); return !!find(this.state.selected, i => this.props.itemKey(i) === key); } toggleItem(item) { if (this.isSelected(item)) { const key = this.props.itemKey(item); this.setState(({ selected }) => ({ selected: filter(selected, i => this.props.itemKey(i) !== key), })); } else { this.setState(({ selected }) => ({ selected: [...selected, item], })); } } save() { this.setState({ saveInProgress: true }, () => { const selectedItems = this.state.selected; Promise.resolve(this.props.save(selectedItems)) .then(() => { this.props.dialog.close(selectedItems); }) .catch(() => { this.setState({ saveInProgress: false }); toastr.error('Failed to save some of selected items.'); }); }); } renderItem(item, isStagedList) { const { renderItem, renderStagedItem } = this.props; const isSelected = this.isSelected(item); const render = isStagedList ? (renderStagedItem || renderItem) : renderItem; const { content, className, isDisabled } = render(item, { isSelected }); return ( this.toggleItem(item)} > {content} ); } render() { const { dialog, dialogTitle, inputPlaceholder, selectedItemsTitle } = this.props; const { loading, saveInProgress, items, selected } = this.state; const hasResults = items.length > 0; return ( this.save()} >
this.search(event.target.value)} placeholder={inputPlaceholder} autoFocus />
{selectedItemsTitle}
{loading && } {!loading && !hasResults && ( )} {!loading && hasResults && ( this.renderItem(item, false)} /> )}
{(selected.length > 0) && ( this.renderItem(item, true)} /> )}
); } } export default wrapDialog(SelectItemsDialog);