From d7f18156c825e7cb0f1918f3746b604c31a3eb9a Mon Sep 17 00:00:00 2001 From: Ian Bolton Date: Wed, 14 Feb 2024 14:56:44 -0500 Subject: [PATCH] - Address PR comment - Cleanup the single select - Disable if no selectable option - Cleanup multiselect filter control Signed-off-by: Ian Bolton --- .../MultiselectFilterControl.tsx | 136 ++++++++++-------- .../FilterToolbar/SelectFilterControl.tsx | 59 ++++---- 2 files changed, 99 insertions(+), 96 deletions(-) diff --git a/client/src/app/components/FilterToolbar/MultiselectFilterControl.tsx b/client/src/app/components/FilterToolbar/MultiselectFilterControl.tsx index 9e82e336a5..f91046e577 100644 --- a/client/src/app/components/FilterToolbar/MultiselectFilterControl.tsx +++ b/client/src/app/components/FilterToolbar/MultiselectFilterControl.tsx @@ -8,7 +8,6 @@ import { SelectGroup, SelectList, SelectOption, - SelectOptionProps, TextInputGroup, TextInputGroupMain, TextInputGroupUtilities, @@ -49,13 +48,25 @@ export const MultiselectFilterControl = ({ const [selectOptions, setSelectOptions] = React.useState< FilterSelectOptionProps[] >(Array.isArray(category.selectOptions) ? category.selectOptions : []); + const hasGroupings = !Array.isArray(selectOptions); + const flatOptions: FilterSelectOptionProps[] = !hasGroupings ? selectOptions : (Object.values(selectOptions).flatMap( (i) => i ) as FilterSelectOptionProps[]); + const getOptionKeyFromOptionValue = (optionValue: string) => + flatOptions.find(({ value }) => value === optionValue)?.key; + + const getOptionValueFromOptionKey = (optionKey: string) => + flatOptions.find(({ key }) => key === optionKey)?.value; + + const getOptionKeyFromChip = (chipDisplayValue: string) => { + return flatOptions.find(({ value }) => value === chipDisplayValue)?.key; + }; + React.useEffect(() => { if (Array.isArray(category.selectOptions)) { setSelectOptions(category.selectOptions); @@ -70,32 +81,24 @@ export const MultiselectFilterControl = ({ const textInputRef = React.useRef(); const [inputValue, setInputValue] = React.useState(""); - const getOptionKeyFromOptionValue = ( - optionValue: string | SelectOptionProps - ) => flatOptions.find((option) => option?.value === optionValue)?.key; - - const getOptionValueFromOptionKey = (optionKey: string) => - flatOptions.find(({ key }) => key === optionKey)?.value; - const onFilterClear = (chip: string | ToolbarChip) => { - const chipKey = typeof chip === "string" ? chip : chip.key; - const newFilterValue = filterValue - ? filterValue.filter((selection) => selection !== chipKey) - : filterValue; + const displayValue = typeof chip === "string" ? chip : chip.key; + const optionKey = getOptionKeyFromChip(displayValue); - setFilterValue(newFilterValue); + if (optionKey) { + const newValue = filterValue?.filter((val) => val !== optionKey) ?? []; + setFilterValue(newValue.length > 0 ? newValue : null); + } }; - // Select expects "selections" to be an array of the "value" props from the relevant optionProps - const selections = filterValue?.map(getOptionValueFromOptionKey) ?? []; - /* * Note: Chips can be a `ToolbarChip` or a plain `string`. Use a hack to split a * selected option in 2 parts. Assuming the option is in the format "Group / Item" * break the text and show a chip with the Item and the Group as a tooltip. */ - const chips = selections.map((s, index) => { - const chip: string = s?.toString() ?? ""; + const chips = filterValue?.map((s, index) => { + const displayValue = getOptionValueFromOptionKey(s); + const chip: string = displayValue?.toString() ?? ""; const idx = chip.indexOf(CHIP_BREAK_DELINEATOR); if (idx > 0) { @@ -113,6 +116,8 @@ export const MultiselectFilterControl = ({ return chip; }); + console.log({ chips, filterValue }); + const renderSelectOptions = ( filter: (option: FilterSelectOptionProps, groupName?: string) => boolean ) => @@ -124,40 +129,64 @@ export const MultiselectFilterControl = ({ .map(([group, options], index) => { const groupFiltered = options?.filter((o) => filter(o, group)) ?? []; - return groupFiltered.length == 0 ? undefined : ( + return groupFiltered.length === 0 ? undefined : ( - {groupFiltered.map((optionProps) => ( - - ))} + {groupFiltered.map((optionProps) => { + const optionKey = getOptionKeyFromOptionValue( + optionProps.value + ); + if (!optionKey) return null; + return ( + + ); + })} ); }) .filter(Boolean) : flatOptions .filter((o) => filter(o)) - .map((optionProps, index) => ( - - {optionProps.value} - - )); + .map((optionProps, index) => { + const optionKey = getOptionKeyFromOptionValue(optionProps.value); + if (!optionKey) return null; + return ( + + {optionProps.value} + + ); + }); const onSelect = (value: string | undefined) => { if (value && value !== "No results") { - const newFilterValue = filterValue ? [...filterValue, value] : [value]; - setFilterValue(newFilterValue); + const optionKey = getOptionKeyFromOptionValue(value); + + if (optionKey) { + let newFilterValue: string[]; + + if (filterValue && filterValue.includes(optionKey)) { + newFilterValue = filterValue.filter((item) => item !== optionKey); + } else { + newFilterValue = filterValue + ? [...filterValue, optionKey] + : [optionKey]; + } + + setFilterValue(newFilterValue); + } } + // Ensure focus remains on the input field textInputRef.current?.focus(); }; @@ -205,7 +234,7 @@ export const MultiselectFilterControl = ({ newSelectOptions = [ { key: "no-results", - isDisabled: true, + isDisabled: false, children: `No results found for "${inputValue}"`, value: "No results", }, @@ -245,27 +274,11 @@ export const MultiselectFilterControl = ({ setSelectOptions(newSelectOptions); setIsFilterDropdownOpen(true); - if ( - isFilterDropdownOpen && - selectedItem && - selectedItem.value !== "no results" - ) { - setInputValue(""); - - const newFilterValue = [...(filterValue || [])]; - const optionValue = getOptionValueFromOptionKey(selectedItem.value); - - if (newFilterValue.includes(optionValue)) { - const indexToRemove = newFilterValue.indexOf(optionValue); - newFilterValue.splice(indexToRemove, 1); - } else { - newFilterValue.push(optionValue); - } - - setFilterValue(newFilterValue); - setIsFilterDropdownOpen(false); + if (!isFilterDropdownOpen) { + setIsFilterDropdownOpen((prev) => !prev); + } else if (selectedItem && selectedItem.value !== "No results") { + onSelect(selectedItem.value); } - break; case "Tab": case "Escape": @@ -295,6 +308,7 @@ export const MultiselectFilterControl = ({ setIsFilterDropdownOpen(!isFilterDropdownOpen); }} isExpanded={isFilterDropdownOpen} + isDisabled={isDisabled || !category.selectOptions.length} isFullWidth > diff --git a/client/src/app/components/FilterToolbar/SelectFilterControl.tsx b/client/src/app/components/FilterToolbar/SelectFilterControl.tsx index e8b6479dda..d343e08de1 100644 --- a/client/src/app/components/FilterToolbar/SelectFilterControl.tsx +++ b/client/src/app/components/FilterToolbar/SelectFilterControl.tsx @@ -5,7 +5,6 @@ import { Select, SelectList, SelectOption, - SelectOptionProps, ToolbarFilter, } from "@patternfly/react-core"; import { IFilterControlProps } from "./FilterControl"; @@ -34,47 +33,36 @@ export const SelectFilterControl = ({ >): JSX.Element | null => { const [isFilterDropdownOpen, setIsFilterDropdownOpen] = React.useState(false); - const getChipFromOptionValue = ( - optionValue: SelectOptionProps | undefined - ) => { - return optionValue ? optionValue.value : ""; - }; + const getOptionKeyFromOptionValue = (optionValue: string) => + category.selectOptions.find(({ value }) => value === optionValue)?.key; - const getOptionKeyFromChip = (chip: string) => - category.selectOptions.find( - (optionProps) => optionProps.value.toString() === chip - )?.key; + const getOptionValueFromOptionKey = (optionKey: string) => + category.selectOptions.find(({ key }) => key === optionKey)?.value; - const getOptionValueFromOptionKey = ( - optionKey: string - ): SelectOptionProps => { - return ( - category.selectOptions.find((optionProps) => { - return optionProps.value === optionKey; - }) || { value: "", children: "", key: "" } - ); - }; + const chips = filterValue?.map((key) => { + const displayValue = getOptionValueFromOptionKey(key); + return displayValue ? displayValue : key; + }); const onFilterSelect = (value: string) => { - setFilterValue(value ? [value] : null); + const optionKey = getOptionKeyFromOptionValue(value); + setFilterValue(optionKey ? [optionKey] : null); setIsFilterDropdownOpen(false); }; const onFilterClear = (chip: string) => { - const optionKey = getOptionKeyFromChip(chip); - const newValue = filterValue - ? filterValue.filter((val) => val !== optionKey) - : []; - setFilterValue(newValue.length > 0 ? newValue : null); + const newValue = filterValue?.filter((val) => val !== chip); + setFilterValue(newValue?.length ? newValue : null); }; - const selections: SelectOptionProps[] = filterValue - ? filterValue.map(getOptionValueFromOptionKey) - : []; - - const chips = selections ? selections.map(getChipFromOptionValue) : []; - const toggle = (toggleRef: React.Ref) => { + let displayText = "Any"; + if (filterValue && filterValue.length > 0) { + const selectedKey = filterValue[0]; + const selectedDisplayValue = getOptionValueFromOptionKey(selectedKey); + displayText = selectedDisplayValue ? selectedDisplayValue : selectedKey; // Fallback to key if display value is not found + } + return ( ({ isExpanded={isFilterDropdownOpen} isDisabled={isDisabled || category.selectOptions.length === 0} > - {filterValue || "Any"} + {displayText} ); }; @@ -114,12 +102,13 @@ export const SelectFilterControl = ({ placeholder="Any" > - {category.selectOptions.map((o: SelectOptionProps, index) => { + {category.selectOptions.map((o, index) => { + const isSelected = filterValue?.includes(o.key); return ( {o.value}