import { filter, find, isEmpty, size } from "lodash"; import React, { useState, useCallback, useEffect } 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 Button from "antd/lib/button"; import { wrap as wrapDialog, DialogPropType } from "@/components/DialogWrapper"; import BigMessage from "@/components/BigMessage"; import LoadingState from "@/components/items-list/components/LoadingState"; import notification from "@/services/notification"; import useSearchResults from "@/lib/hooks/useSearchResults"; import "./SelectItemsDialog.less"; function ItemsList({ items, renderItem, onItemClick }) { const renderListItem = useCallback( item => { const { content, className, isDisabled } = renderItem(item); return ( onItemClick(item)}> {content} ); }, [renderItem, onItemClick] ); return ; } ItemsList.propTypes = { items: PropTypes.array, renderItem: PropTypes.func, onItemClick: PropTypes.func, }; ItemsList.defaultProps = { items: [], renderItem: () => {}, onItemClick: () => {}, }; function SelectItemsDialog({ dialog, dialogTitle, inputPlaceholder, itemKey, renderItem, renderStagedItem, searchItems, selectedItemsTitle, width, showCount, extraFooterContent, }) { const [selectedItems, setSelectedItems] = useState([]); const [search, items, isLoading] = useSearchResults(searchItems, { initialResults: [] }); const hasResults = items.length > 0; useEffect(() => { search(); }, [search]); const isItemSelected = useCallback( item => { const key = itemKey(item); return !!find(selectedItems, i => itemKey(i) === key); }, [selectedItems, itemKey] ); const toggleItem = useCallback( item => { if (isItemSelected(item)) { const key = itemKey(item); setSelectedItems(filter(selectedItems, i => itemKey(i) !== key)); } else { setSelectedItems([...selectedItems, item]); } }, [selectedItems, itemKey, isItemSelected] ); const save = useCallback(() => { dialog.close(selectedItems).catch(error => { if (error) { notification.error("Failed to save some of selected items."); } }); }, [dialog, selectedItems]); return ( {extraFooterContent} }>
search(event.target.value)} placeholder={inputPlaceholder} aria-label={inputPlaceholder} autoFocus />
{renderStagedItem && (
{selectedItemsTitle}
)}
{isLoading && } {!isLoading && !hasResults && ( )} {!isLoading && hasResults && ( renderItem(item, { isSelected: isItemSelected(item) })} onItemClick={toggleItem} /> )}
{renderStagedItem && (
{selectedItems.length > 0 && ( renderStagedItem(item, { isSelected: true })} onItemClick={toggleItem} /> )}
)}
); } SelectItemsDialog.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, width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), extraFooterContent: PropTypes.node, showCount: PropTypes.bool, }; SelectItemsDialog.defaultProps = { dialogTitle: "Add Items", inputPlaceholder: "Search...", selectedItemsTitle: "Selected items", itemKey: item => item.id, renderItem: () => "", renderStagedItem: null, // hidden by default width: "80%", extraFooterContent: null, showCount: false, }; export default wrapDialog(SelectItemsDialog);