import React, { useRef, useState, useEffect, useCallback } from "react"; import PropTypes from "prop-types"; import Header from "./Header"; import Search from "./Search"; import Group from "./Group"; import Option from "./Option"; import Footer from "./Footer"; import { FixedSizeList as List } from "react-window"; import InfiniteLoader from "react-window-infinite-loader"; import AutoSizer from "react-virtualized-auto-sizer"; import ReactTooltip from "react-tooltip"; import Avatar from "@appserver/components/avatar"; import Checkbox from "@appserver/components/checkbox"; import Text from "@appserver/components/text"; import Tooltip from "@appserver/components/tooltip"; import CustomScrollbarsVirtualList from "@appserver/components/scrollbar/custom-scrollbars-virtual-list"; import StyledSelector from "./StyledSelector"; const convertGroups = (items) => { if (!items) return []; const wrappedGroups = items.map(convertGroup); return wrappedGroups; }; const convertGroup = (group) => { return { key: group.key, label: `${group.label} (${group.total})`, total: group.total, selected: 0, }; }; const getCurrentGroup = (items) => { const currentGroup = items.length > 0 ? items[0] : {}; return currentGroup; }; const Selector = (props) => { const { groups, isDisabled, isMultiSelect, hasNextPage, options, isNextPageLoading, loadNextPage, selectedOptions, selectedGroups, searchPlaceHolderLabel, emptySearchOptionsLabel, emptyOptionsLabel, loadingLabel, onSelect, getOptionTooltipContent, onSearchChanged, onGroupChanged, size, allowGroupSelection, embeddedComponent, showCounter, onArrowClick, headerLabel, } = props; const listOptionsRef = useRef(null); useEffect(() => { Object.keys(currentGroup).length === 0 && setCurrentGroup(getCurrentGroup(convertGroups(groups))); resetCache(); }, [searchValue, currentGroup, hasNextPage]); const [selectedOptionList, setSelectedOptionList] = useState( selectedOptions || [] ); const [selectedGroupList, setSelectedGroupList] = useState( selectedGroups || [] ); const [searchValue, setSearchValue] = useState(""); const [currentGroup, setCurrentGroup] = useState( getCurrentGroup(convertGroups(groups)) ); const [groupHeader, setGroupHeader] = useState(null); useEffect(() => { if (groups.length === 1) setGroupHeader(groups[0]); }, [groups]); // Every row is loaded except for our loading indicator row. const isItemLoaded = useCallback( (index) => { return !hasNextPage || index < options.length; }, [hasNextPage, options] ); const onOptionChange = useCallback( (index, isChecked) => { const option = options[index]; const newSelected = !isChecked ? [option, ...selectedOptionList] : selectedOptionList.filter((el) => el.key !== option.key); setSelectedOptionList(newSelected); if (!option.groups) return; const newSelectedGroups = []; const removedSelectedGroups = []; if (isChecked) { option.groups.forEach((g) => { let index = selectedGroupList.findIndex((sg) => sg.key === g); if (index > -1) { // exists const selectedGroup = selectedGroupList[index]; const newSelected = selectedGroup.selected + 1; newSelectedGroups.push( Object.assign({}, selectedGroup, { selected: newSelected, }) ); } else { index = groups.findIndex((sg) => sg.key === g); if (index < 0) return; const notSelectedGroup = convertGroup(groups[index]); newSelectedGroups.push( Object.assign({}, notSelectedGroup, { selected: 1, }) ); } }); } else { option.groups.forEach((g) => { let index = selectedGroupList.findIndex((sg) => sg.key === g); if (index > -1) { // exists const selectedGroup = selectedGroupList[index]; const newSelected = selectedGroup.selected - 1; if (newSelected > 0) { newSelectedGroups.push( Object.assign({}, selectedGroup, { selected: newSelected, }) ); } else { removedSelectedGroups.push( Object.assign({}, selectedGroup, { selected: newSelected, }) ); } } }); } selectedGroupList.forEach((g) => { const indexNew = newSelectedGroups.findIndex((sg) => sg.key === g.key); if (indexNew === -1) { const indexRemoved = removedSelectedGroups.findIndex( (sg) => sg.key === g.key ); if (indexRemoved === -1) { newSelectedGroups.push(g); } } }); setSelectedGroupList(newSelectedGroups); }, [options, selectedOptionList, groups, selectedGroupList] ); const resetCache = useCallback(() => { if (listOptionsRef && listOptionsRef.current) { listOptionsRef.current.resetloadMoreItemsCache(true); } }, [listOptionsRef]); const onSearchChange = useCallback( (value) => { setSearchValue(value); onSearchChanged && onSearchChanged(value); }, [onSearchChanged] ); const onSearchReset = useCallback(() => { onSearchChanged && onSearchChange(""); }, [onSearchChanged]); const isOptionChecked = useCallback( (option) => { const checked = selectedOptionList.findIndex((el) => el.key === option.key) > -1 || (option.groups && option.groups.filter((gKey) => { const selectedGroup = selectedGroupList.find( (sg) => sg.key === gKey ); if (!selectedGroup) return false; return selectedGroup.total === selectedGroup.selected; }).length > 0); return checked; }, [selectedOptionList, selectedGroupList] ); const onSelectOptions = (items) => { onSelect && onSelect(items); }; const onAddClick = useCallback(() => { onSelectOptions(selectedOptionList); }, [selectedOptionList]); const onLinkClick = useCallback( (index) => { const option = options[index]; if (!option) return; onSelectOptions([option]); }, [options] ); // Render an item or a loading indicator. // eslint-disable-next-line react/prop-types const renderOption = useCallback( ({ index, style }) => { const isLoaded = isItemLoaded(index); if (!isLoaded) { return