// Copyright 2020 BMW Group // // Licensed under the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. You may obtain // a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations // under the License. import React, { useState } from 'react' import PropTypes from 'prop-types' import { Button, ButtonVariant, Dropdown, DropdownItem, DropdownPosition, DropdownToggle, InputGroup, TextInput, Toolbar, ToolbarContent, ToolbarFilter, ToolbarGroup, ToolbarItem, ToolbarToggleGroup, } from '@patternfly/react-core' import { FilterIcon, SearchIcon } from '@patternfly/react-icons' import { FilterSelect } from './filters/Select' import { FilterTernarySelect } from './filters/TernarySelect' import { FilterCheckbox } from './filters/Checkbox' function FilterToolbar(props) { const [isCategoryDropdownOpen, setIsCategoryDropdownOpen] = useState(false) const [currentCategory, setCurrentCategory] = useState( props.filterCategories[0].title ) const [inputValue, setInputValue] = useState('') function handleCategoryToggle(isOpen) { setIsCategoryDropdownOpen(isOpen) } function handleCategorySelect(event) { setCurrentCategory(event.target.innerText) setIsCategoryDropdownOpen(!isCategoryDropdownOpen) } function handleInputChange(newValue) { setInputValue(newValue) } function handleInputSend(event, category) { const { onFilterChange, filters } = props // In case the event comes from a key press, only accept "Enter" if (event.key && event.key !== 'Enter') { return } // Ignore empty values if (!inputValue) { return } const prevFilters = filters[category.key] const newFilters = { ...filters, [category.key]: prevFilters.includes(inputValue) ? prevFilters : [...prevFilters, inputValue], } // Clear the input field setInputValue('') // Notify the parent component about the filter change onFilterChange(newFilters) } function handleDelete(type = '', id = '', category) { const { filterCategories, filters, onFilterChange } = props // Usually the type contains the category for which a chip should be deleted // If the type is set, we got a delete() call for a single chip. The type // reflects the name of the Chip group which does not necessarily go in hand // with our category keys. Thus, we use the category to identify the correct // filter to be updated/removed. let newFilters = {} if (type) { if (category.type === 'ternary') { newFilters = { ...filters, [category.key]: [], } } else { newFilters = { ...filters, [category.key]: filters[category.key].filter((s) => s !== id), } } } else { // Delete the values for each filter category newFilters = filterCategories.reduce((filterDict, category) => { filterDict[category.key] = [] return filterDict }, {}) } // Notify the parent component about the filter change onFilterChange(newFilters) } function renderCategoryDropdown() { const { filterCategories } = props return ( {currentCategory} } isOpen={isCategoryDropdownOpen} dropdownItems={filterCategories.filter( (category) => (category.type === 'search' || category.type === 'select' || category.type === 'ternary' || category.type === 'checkbox') ).map((category) => ( {category.title} ))} style={{ width: '100%' }} /> ) } function renderFilterInput(category, filters) { const { onFilterChange } = props if (category.type === 'search') { return ( handleInputSend(event, category)} /> ) } else if (category.type === 'select') { return ( ) } else if (category.type === 'ternary') { return ( ) } else if (category.type === 'checkbox') { return (
) } } function renderFilterDropdown() { const { filterCategories, filters } = props return ( <> {filterCategories.map((category) => ( handleDelete(type, id, category)} categoryName={category.title} showToolbarItem={currentCategory === category.title} > {renderFilterInput(category, filters)} ))} ) } return ( <> } breakpoint="md"> {renderCategoryDropdown()} {renderFilterDropdown()} ) } FilterToolbar.propTypes = { onFilterChange: PropTypes.func.isRequired, filters: PropTypes.object.isRequired, filterCategories: PropTypes.array.isRequired, } function getChipsFromFilters(filters, category) { if (category.type === 'ternary') { switch ([...filters[category.key]].pop()) { case 1: case '1': return ['True',] case 0: case '0': return ['False',] default: return [] } } else { return filters[category.key] } } function getFiltersFromUrl(location, filterCategories) { const urlParams = new URLSearchParams(location.search) const _filters = filterCategories.reduce((filterDict, item) => { // Initialize each filter category with an empty list filterDict[item.key] = [] // And update the list with each matching element from the URL query urlParams.getAll(item.key).forEach((param) => { if (item.type === 'checkbox') { switch (param) { case '1': filterDict[item.key].push(1) break case '0': filterDict[item.key].push(0) break default: break } } else { filterDict[item.key].push(param) } }) return filterDict }, {}) const pagination_options = { skip: urlParams.getAll('skip') ? urlParams.getAll('skip') : [0,], limit: urlParams.getAll('limit') ? urlParams.getAll('limit') : [50,], } const filters = { ..._filters, ...pagination_options } return filters } function writeFiltersToUrl(filters, location, history) { // Build new URL parameters from the filters in state const searchParams = new URLSearchParams('') Object.keys(filters).map((key) => { filters[key].forEach((value) => { searchParams.append(key, value) }) return searchParams }) history.push({ pathname: location.pathname, search: searchParams.toString(), }) } function buildQueryString(filters, excludeResults) { let queryString = '&complete=true' let resultFilter = false if (filters) { Object.keys(filters).map((key) => { filters[key].forEach((value) => { if (key === 'result') { resultFilter = true } queryString += '&' + key + '=' + value }) return queryString }) } if (excludeResults && !resultFilter) { queryString += '&exclude_result=SKIPPED' } return queryString } export { buildQueryString, FilterToolbar, getFiltersFromUrl, writeFiltersToUrl }