diff --git a/src/components/Toolbar/DatePickerFilter.js b/src/components/Toolbar/DatePickerFilter.js
new file mode 100644
index 0000000000..116661e5a2
--- /dev/null
+++ b/src/components/Toolbar/DatePickerFilter.js
@@ -0,0 +1,78 @@
+import React, { useState } from 'react'
+import PropTypes from 'prop-types'
+import { withMsg } from '_/intl'
+import {
+ DatePicker,
+ InputGroup,
+ ToolbarFilter,
+} from '@patternfly/react-core'
+
+import moment from 'moment'
+
+const DatePickerFilter = ({
+ filterId,
+ selectedFilters,
+ showToolbarItem,
+ title,
+ onFilterUpdate,
+ msg,
+}) => {
+ const dateFormat = moment.localeData().longDateFormat('L')
+ const formatDate = date => moment(date).format(dateFormat)
+ const parseDate = str => moment(str, dateFormat).toDate()
+ const isValidDate = date => moment(date, dateFormat).isValid()
+ const toISO = str => moment(str, dateFormat).format('YYYY-MM-DD')
+ const fromISO = str => moment(str, 'YYYY-MM-DD').format(dateFormat)
+
+ const [date, setDate] = useState(toISO(formatDate(Date.now())))
+
+ const clearSingleDate = (option) => {
+ console.warn('clearSingle ', option)
+ const fixed = toISO(option)
+ onFilterUpdate([...selectedFilters?.filter(d => d !== fixed)])
+ }
+
+ const onDateChange = (inputDate, newDate) => {
+ if (isValidDate(inputDate)) {
+ const fixed = toISO(inputDate)
+ setDate(fixed)
+ onFilterUpdate([...selectedFilters?.filter(d => d !== fixed), fixed])
+ }
+ }
+
+ return (
+ clearSingleDate(option)}
+ deleteChipGroup={() => onFilterUpdate([])}
+ categoryName={title}
+ showToolbarItem={showToolbarItem}
+ >
+
+
+
+
+ )
+}
+
+DatePickerFilter.propTypes = {
+ filterId: PropTypes.string.isRequired,
+ selectedFilters: PropTypes.array.isRequired,
+ showToolbarItem: PropTypes.bool.isRequired,
+ title: PropTypes.string.isRequired,
+ onFilterUpdate: PropTypes.func.isRequired,
+
+ msg: PropTypes.object.isRequired,
+}
+
+export default withMsg(DatePickerFilter)
diff --git a/src/components/Toolbar/Filters.js b/src/components/Toolbar/Filters.js
new file mode 100644
index 0000000000..f8b108fdfb
--- /dev/null
+++ b/src/components/Toolbar/Filters.js
@@ -0,0 +1,141 @@
+import React, { useState } from 'react'
+import PropTypes from 'prop-types'
+import { withMsg } from '_/intl'
+import {
+ Button,
+ ButtonVariant,
+ Dropdown,
+ DropdownItem,
+ DropdownPosition,
+ DropdownToggle,
+ InputGroup,
+ TextInput,
+ ToolbarGroup,
+ ToolbarFilter,
+ ToolbarItem,
+ ToolbarToggleGroup,
+} from '@patternfly/react-core'
+
+import { FilterIcon, SearchIcon } from '@patternfly/react-icons/dist/esm/icons'
+
+import DatePickerFilter from './DatePickerFilter'
+import SelectFilter from './SelectFilter'
+
+const Filters = ({ msg, locale, selectedFilters, onFilterUpdate, filterTypes, textBasedFilterId }) => {
+ const [currentFilterType, setCurrentFilterType] = useState(filterTypes[0])
+ const [expanded, setExpanded] = useState(false)
+ const [inputValue, setInputValue] = useState('')
+
+ const nameFilter = filterTypes.find(({ id }) => id === textBasedFilterId)
+ const labelToFilter = (label) => filterTypes.find(({ title }) => title === label) ?? currentFilterType
+
+ const onFilterTypeSelect = (event) => {
+ setCurrentFilterType(labelToFilter(event?.target?.innerText))
+ setExpanded(!expanded)
+ }
+ const onFilterTypeToggle = () => setExpanded(!expanded)
+ const onNameInput = (event) => {
+ if ((event.key && event.key !== 'Enter') ||
+ !inputValue ||
+ selectedFilters?.[textBasedFilterId]?.includes(inputValue)) {
+ return
+ }
+ onFilterUpdate({ ...selectedFilters, [textBasedFilterId]: [...(selectedFilters?.[textBasedFilterId] ?? []), inputValue] })
+ setInputValue('')
+ }
+
+ return (
+ } breakpoint="xl">
+
+
+
+ {currentFilterType.title}
+
+ )}
+ isOpen={expanded}
+ style={{ width: '100%' }}
+ dropdownItems={
+ filterTypes.map(({ id, title }) =>
+ {title})
+ }
+ />
+
+ onFilterUpdate({
+ ...selectedFilters,
+ [textBasedFilterId]: selectedFilters?.[textBasedFilterId]?.filter?.(value => value !== option) ?? [],
+ })}
+ deleteChipGroup={() => onFilterUpdate({ ...selectedFilters, [textBasedFilterId]: [] })}
+ categoryName={nameFilter.title}
+ showToolbarItem={currentFilterType.id === textBasedFilterId}
+ >
+
+
+
+
+
+ {
+ filterTypes.filter(({ datePicker }) => datePicker).map(({ id: filterId, title }) => (
+ {
+ console.warn('filtersToSave', filtersToSave)
+ onFilterUpdate({ ...selectedFilters, [filterId]: filtersToSave })
+ }
+ }
+ />
+ ))
+ }
+ {filterTypes.filter(({ filterValues }) => !!filterValues?.length)
+ ?.map(({ id, filterValues, placeholder, title }) => (
+ onFilterUpdate({ ...selectedFilters, [id]: filtersToSave })}
+ title={title}
+ placeholderText={placeholder}
+ />
+ )
+ )}
+
+
+ )
+}
+
+Filters.propTypes = {
+ selectedFilters: PropTypes.object.isRequired,
+ filterTypes: PropTypes.array.isRequired,
+ textBasedFilterId: PropTypes.string.isRequired,
+ onFilterUpdate: PropTypes.func.isRequired,
+ msg: PropTypes.object.isRequired,
+ locale: PropTypes.string.isRequired,
+}
+
+export default withMsg(Filters)
diff --git a/src/components/Toolbar/SelectFilter.js b/src/components/Toolbar/SelectFilter.js
new file mode 100644
index 0000000000..7819de3149
--- /dev/null
+++ b/src/components/Toolbar/SelectFilter.js
@@ -0,0 +1,79 @@
+import React, { useState } from 'react'
+import PropTypes from 'prop-types'
+import {
+ Select,
+ SelectOption,
+ SelectVariant,
+ ToolbarFilter,
+} from '@patternfly/react-core'
+
+const SelectFilter = ({ filterIds = [], setFilters, allSupportedFilters = [], title, placeholderText, filterColumnId, showToolbarItem }) => {
+ const [isExpanded, setExpanded] = useState(false)
+
+ // one label can map to many IDs so it's easier work with labels
+ // and reverse map label-to-IDs on save
+ const toChip = ({ title }) => title
+ const toOption = ({ title }) => title
+ const toOptionNode = ({ title }) =>
+
+
+ // titles are guaranteed to be unique
+ // return first filter with matching title
+ const labelToIds = (title) => {
+ const [{ ids = {} } = {}] = allSupportedFilters.filter(filter => filter.title === title) || []
+ return ids
+ }
+ const selectedFilters = allSupportedFilters.filter(({ ids }) => filterIds.find(id => ids[id]))
+ const deleteFilter = (title) => {
+ const ids = labelToIds(title)
+ // delete all filter IDs linked to provided title
+ setFilters(filterIds.filter(id => !ids[id]))
+ }
+
+ const addFilter = (title) => {
+ const ids = labelToIds(title)
+ // add all filter IDs linked
+ setFilters([...filterIds, ...Object.keys(ids)])
+ }
+ return (
+ deleteFilter(option)}
+ deleteChipGroup={() => setFilters([])}
+ categoryName={title}
+ showToolbarItem={showToolbarItem}
+ >
+
+
+ )
+}
+
+SelectFilter.propTypes = {
+ filterIds: PropTypes.array.isRequired,
+ allSupportedFilters: PropTypes.array.isRequired,
+ setFilters: PropTypes.func.isRequired,
+ title: PropTypes.string.isRequired,
+ placeholderText: PropTypes.string.isRequired,
+ filterColumnId: PropTypes.string.isRequired,
+ showToolbarItem: PropTypes.bool.isRequired,
+}
+
+export default SelectFilter
diff --git a/src/components/Toolbar/Sort.js b/src/components/Toolbar/Sort.js
new file mode 100644
index 0000000000..8c79617108
--- /dev/null
+++ b/src/components/Toolbar/Sort.js
@@ -0,0 +1,83 @@
+import React, { useState } from 'react'
+import PropTypes from 'prop-types'
+
+import { withMsg } from '_/intl'
+import { translate } from '_/helpers'
+import {
+ Button,
+ OptionsMenu,
+ OptionsMenuItemGroup,
+ OptionsMenuSeparator,
+ OptionsMenuItem,
+ OptionsMenuToggle,
+ ToolbarGroup,
+ ToolbarItem,
+} from '@patternfly/react-core'
+import { SortAmountDownIcon, SortAmountDownAltIcon } from '@patternfly/react-icons/dist/esm/icons'
+
+const Sort = ({ sort, msg, onSortChange, SortFields }) => {
+ const { id: enabledSortId, isAsc } = sort || {}
+ const [expanded, setExpanded] = useState(false)
+
+ const menuItems = [
+
+ {Object.values(SortFields)
+ .map(type => ({ ...type, title: translate({ ...type.messageDescriptor, msg }) }))
+ .map(({ title, id, messageDescriptor }) => (
+ onSortChange({ ...sort, id, messageDescriptor })}
+ >
+ {title}
+
+ ))
+ }
+ ,
+ ,
+
+ onSortChange({ ...sort, isAsc: true })} isSelected={isAsc} id="ascending" key="ascending">{msg.ascending()}
+ onSortChange({ ...sort, isAsc: false })} isSelected={!isAsc} id="descending" key="descending">{msg.descending()}
+ ,
+ ]
+
+ return (
+
+
+ setExpanded(!expanded)}
+ toggleTemplate={sort?.messageDescriptor ? translate({ ...sort.messageDescriptor, msg }) : msg.sortBy()}
+ />
+ )}
+ isGrouped
+ />
+
+
+
+
+
+ )
+}
+
+Sort.propTypes = {
+ sort: PropTypes.shape({
+ id: PropTypes.string.isRequired,
+ messageDescriptor: PropTypes.object.isRequired,
+ isAsc: PropTypes.bool,
+ }),
+ SortFields: PropTypes.objectOf(PropTypes.shape({
+ id: PropTypes.string.isRequired,
+ messageDescriptor: PropTypes.object.isRequired,
+ })).isRequired,
+ onSortChange: PropTypes.func.isRequired,
+ msg: PropTypes.object.isRequired,
+}
+
+export default withMsg(Sort)
diff --git a/src/components/Toolbar/VmFilters.js b/src/components/Toolbar/VmFilters.js
index 383dd7d485..1f9116cdf2 100644
--- a/src/components/Toolbar/VmFilters.js
+++ b/src/components/Toolbar/VmFilters.js
@@ -1,28 +1,11 @@
-import React, { useState, useMemo } from 'react'
+import React, { useMemo } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { enumMsg, withMsg } from '_/intl'
import { saveVmsFilters } from '_/actions'
import { localeCompare, toJS } from '_/helpers'
-import {
- Button,
- ButtonVariant,
- Dropdown,
- DropdownItem,
- DropdownPosition,
- DropdownToggle,
- InputGroup,
- Select,
- SelectOption,
- SelectVariant,
- TextInput,
- ToolbarGroup,
- ToolbarFilter,
- ToolbarItem,
- ToolbarToggleGroup,
-} from '@patternfly/react-core'
-import { FilterIcon, SearchIcon } from '@patternfly/react-icons/dist/esm/icons'
+import Filters from './Filters'
const STATUS = 'status'
const OS = 'os'
@@ -79,74 +62,6 @@ const composeOs = (msg, locale, operatingSystems) => {
})
}
-const Filter = ({ filterIds = [], setFilters, allSupportedFilters = [], title, filterColumnId, showToolbarItem }) => {
- const [isExpanded, setExpanded] = useState(false)
-
- // one label can map to many IDs so it's easier work with labels
- // and reverse map label-to-IDs on save
- const toChip = ({ title }) => title
- const toOption = ({ title }) => title
- const toOptionNode = ({ title }) =>
-
-
- // titles are guaranteed to be unique
- // return first filter with matching title
- const labelToIds = (title) => {
- const [{ ids = {} } = {}] = allSupportedFilters.filter(filter => filter.title === title) || []
- return ids
- }
- const selectedFilters = allSupportedFilters.filter(({ ids }) => filterIds.find(id => ids[id]))
- const deleteFilter = (title) => {
- const ids = labelToIds(title)
- // delete all filter IDs linked to provided title
- setFilters(filterIds.filter(id => !ids[id]))
- }
-
- const addFilter = (title) => {
- const ids = labelToIds(title)
- // add all filter IDs linked
- setFilters([...filterIds, ...Object.keys(ids)])
- }
- return (
- deleteFilter(option)}
- deleteChipGroup={() => setFilters([])}
- categoryName={filterColumnId}
- showToolbarItem={showToolbarItem}
- >
-
-
- )
-}
-
-Filter.propTypes = {
- filterIds: PropTypes.array.isRequired,
- allSupportedFilters: PropTypes.array.isRequired,
- setFilters: PropTypes.func.isRequired,
- title: PropTypes.string.isRequired,
- filterColumnId: PropTypes.string.isRequired,
- showToolbarItem: PropTypes.bool.isRequired,
-}
-
const VmFilters = ({ msg, locale, operatingSystems, selectedFilters, onFilterUpdate }) => {
const filterTypes = useMemo(() => [
{
@@ -157,91 +72,13 @@ const VmFilters = ({ msg, locale, operatingSystems, selectedFilters, onFilterUpd
composeStatus(msg, locale),
composeOs(msg, locale, operatingSystems),
], [msg, locale, operatingSystems])
- const [currentFilterType, setCurrentFilterType] = useState(filterTypes[0])
- const [expanded, setExpanded] = useState(false)
- const [inputValue, setInputValue] = useState('')
-
- const nameFilter = filterTypes.find(({ id }) => id === NAME)
- const labelToFilter = (label) => filterTypes.find(({ title }) => title === label) ?? currentFilterType
-
- const onFilterTypeSelect = (event) => {
- setCurrentFilterType(labelToFilter(event?.target?.innerText))
- setExpanded(!expanded)
- }
- const onFilterTypeToggle = () => setExpanded(!expanded)
- const onNameInput = (event) => {
- if ((event.key && event.key !== 'Enter') ||
- !inputValue ||
- selectedFilters?.[NAME]?.includes(inputValue)) {
- return
- }
- onFilterUpdate({ ...selectedFilters, [NAME]: [...(selectedFilters?.[NAME] ?? []), inputValue] })
- setInputValue('')
- }
-
return (
- } breakpoint="xl">
-
-
-
- {currentFilterType.title}
-
- )}
- isOpen={expanded}
- style={{ width: '100%' }}
- dropdownItems={
- filterTypes.map(({ id, title }) =>
- {title})
- }
- />
-
- onFilterUpdate({
- ...selectedFilters,
- [NAME]: selectedFilters?.[NAME]?.filter?.(value => value !== option) ?? [],
- })}
- deleteChipGroup={() => onFilterUpdate({ ...selectedFilters, [NAME]: [] })}
- categoryName={NAME}
- showToolbarItem={currentFilterType.id === NAME}
- >
-
-
-
-
-
- {filterTypes.filter(({ id }) => id !== NAME)?.map(({ id, filterValues, placeholder }) => (
- onFilterUpdate({ ...selectedFilters, [id]: filtersToSave })}
- title={placeholder}
- />
- )
- )}
-
-
+
)
}
diff --git a/src/components/Toolbar/VmSort.js b/src/components/Toolbar/VmSort.js
index 7f0d8cc1ab..6ec0e64670 100644
--- a/src/components/Toolbar/VmSort.js
+++ b/src/components/Toolbar/VmSort.js
@@ -1,83 +1,20 @@
-import React, { useState } from 'react'
+import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
-
-import { setVmSort } from '_/actions'
+import Sort from './Sort'
import { SortFields } from '_/utils'
+import { setVmSort } from '_/actions'
import { withMsg } from '_/intl'
-import { translate } from '_/helpers'
-import {
- Button,
- OptionsMenu,
- OptionsMenuItemGroup,
- OptionsMenuSeparator,
- OptionsMenuItem,
- OptionsMenuToggle,
- ToolbarGroup,
- ToolbarItem,
-} from '@patternfly/react-core'
-import { SortAmountDownIcon, SortAmountDownAltIcon } from '@patternfly/react-icons/dist/esm/icons'
-const VmSort = ({ sort, msg, onSortChange }) => {
- const { id: enabledSortId, isAsc } = sort
- const [expanded, setExpanded] = useState(false)
-
- const menuItems = [
-
- {Object.values(SortFields)
- .map(type => ({ ...type, title: translate({ ...type.messageDescriptor, msg }) }))
- .map(({ title, id, messageDescriptor }) => (
- onSortChange({ ...sort, id, messageDescriptor })}
- >
- {title}
-
- ))
- }
- ,
- ,
-
- onSortChange({ ...sort, isAsc: true })} isSelected={isAsc} id="ascending" key="ascending">{msg.ascending()}
- onSortChange({ ...sort, isAsc: false })} isSelected={!isAsc} id="descending" key="descending">{msg.descending()}
- ,
- ]
-
- return (
-
-
- setExpanded(!expanded)}
- toggleTemplate={sort?.messageDescriptor ? translate({ ...sort.messageDescriptor, msg }) : msg.sortBy()}
- />
- )}
- isGrouped
- />
-
-
-
-
-
- )
-}
+const VmSort = ({ sort, onSortChange }) =>
VmSort.propTypes = {
sort: PropTypes.shape({
id: PropTypes.string.isRequired,
messageDescriptor: PropTypes.object.isRequired,
- isNumeric: PropTypes.bool,
isAsc: PropTypes.bool,
}).isRequired,
onSortChange: PropTypes.func.isRequired,
- msg: PropTypes.object.isRequired,
}
export default connect(
diff --git a/src/utils/vms-sort.js b/src/utils/vms-sort.js
index b683a07ae1..eba1f6c366 100644
--- a/src/utils/vms-sort.js
+++ b/src/utils/vms-sort.js
@@ -11,17 +11,14 @@ const getFieldValueMap = (msg) => ({
export const SortFields = {
NAME: {
id: 'name',
- isNumeric: false,
messageDescriptor: { id: 'name' },
},
OS: {
id: 'os',
- isNumeric: false,
messageDescriptor: { id: 'operatingSystem' },
},
STATUS: {
id: 'status',
- isNumeric: false,
messageDescriptor: { id: 'status' },
},
}