Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

4002 - TablePaginated: Filter by User Role + Admin users 'Role' #4043

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import 'src/client/style/partials';

.select__multiValue-label {
padding-right: $spacing-xxxs;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import './MultiValueSummary.scss'
import React from 'react'
import { useTranslation } from 'react-i18next'
import { MultiValueProps as OriginalMultiValueProps } from 'react-select'

import { SelectProps } from 'client/components/Inputs/Select/types'

type MultiValueProps = OriginalMultiValueProps & Pick<SelectProps, 'multiLabelSummaryKey'>

export const MultiValueSummary: React.FC<MultiValueProps> = (props) => {
const { getValue, index, multiLabelSummaryKey } = props
const count = getValue().length
const { t } = useTranslation()

const displayCountLabel = index === 0

if (displayCountLabel) {
return <span className="select__multiValue-label">{t(multiLabelSummaryKey, { count })}</span>
}

return null
}

export default MultiValueSummary
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { MultiValueSummary } from './MultiValueSummary'
1 change: 1 addition & 0 deletions src/client/components/Inputs/Select/Indicators/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { ClearIndicator, DropdownIndicator, IndicatorsContainer } from './Indicators'
export { MultiSelectOption } from './MultiSelectOption'
export { MultiValueSummary } from './MultiValueSummary'
4 changes: 3 additions & 1 deletion src/client/components/Inputs/Select/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { useValue } from './hooks/useValue'
import { SelectProps } from './types'

const Select: React.FC<SelectProps> = (props) => {
const { disabled, isClearable, isMulti, maxMenuHeight, placeholder } = props
const { disabled, isClearable, isMulti, maxMenuHeight, onMenuClose, onMenuOpen, placeholder } = props

const classNames = useClassNames(props)
const components = useComponents(props)
Expand All @@ -32,6 +32,8 @@ const Select: React.FC<SelectProps> = (props) => {
menuPlacement="auto"
menuPosition="fixed"
onChange={onChange}
onMenuClose={onMenuClose}
onMenuOpen={onMenuOpen}
options={options}
placeholder={placeholder ?? ''}
value={value}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { useMemo } from 'react'
import { Props as ReactSelectProps } from 'react-select'
import React, { useMemo } from 'react'
import { MultiValueProps, Props as ReactSelectProps } from 'react-select'

import { Objects } from 'utils/objects'

import {
ClearIndicator,
DropdownIndicator,
IndicatorsContainer,
MultiSelectOption,
MultiValueSummary,
} from 'client/components/Inputs/Select/Indicators'
import { SelectProps } from 'client/components/Inputs/Select/types'

type Returned = ReactSelectProps['components']

export const useComponents = (props: SelectProps): Returned => {
const { isMulti } = props
const { isMulti, multiLabelSummaryKey } = props

return useMemo<Returned>(() => {
const components: Returned = {
Expand All @@ -22,7 +25,13 @@ export const useComponents = (props: SelectProps): Returned => {
IndicatorSeparator: null,
}
if (isMulti) components.Option = MultiSelectOption
if (isMulti && !Objects.isEmpty(multiLabelSummaryKey)) {
components.MultiValue = (originalMultiValueProps: MultiValueProps) => (
// eslint-disable-next-line react/jsx-props-no-spreading
<MultiValueSummary {...originalMultiValueProps} multiLabelSummaryKey={multiLabelSummaryKey} />
)
}

return components
}, [isMulti])
}, [isMulti, multiLabelSummaryKey])
}
6 changes: 5 additions & 1 deletion src/client/components/Inputs/Select/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@ export type OptionsOrGroups = readonly (Option | OptionsGroup)[]

export type ValueInput = string | Array<string> | null

type SelectBaseProps = Pick<ReactSelectProps, 'isClearable' | 'isMulti' | 'maxMenuHeight' | 'placeholder'>
type SelectBaseProps = Pick<
ReactSelectProps,
'isClearable' | 'isMulti' | 'maxMenuHeight' | 'onMenuOpen' | 'onMenuClose' | 'placeholder'
>
type SelectClassNamesProps = {
classNames?: { container?: string }
}
export type SelectProps = SelectBaseProps &
SelectClassNamesProps & {
disabled?: boolean
multiLabelSummaryKey?: string
onChange: (value: string | Array<string> | null) => void
options: OptionsOrGroups
toggleAll?: boolean
Expand Down
1 change: 1 addition & 0 deletions src/client/components/TablePaginated/Filters/Filters.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
align-items: center;
display: flex;
gap: $spacing-xxs;
height: 30px;
width: 100%;

svg.icon_filter {
Expand Down
4 changes: 3 additions & 1 deletion src/client/components/TablePaginated/Filters/Filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ import React from 'react'
import { TablePaginatedFilterType } from 'meta/tablePaginated'

import Icon from 'client/components/Icon'
import MultiSelect from 'client/components/TablePaginated/Filters/MultiSelect/MultiSelect'
import Text from 'client/components/TablePaginated/Filters/Text/Text'
import { TablePaginatedFilter } from 'client/components/TablePaginated/types'

const componentsByFilterType: Record<
TablePaginatedFilterType,
React.FC<TablePaginatedFilter<TablePaginatedFilterType> & { path: string }>
> = {
[TablePaginatedFilterType.TEXT]: Text,
[TablePaginatedFilterType.MULTI_SELECT]: MultiSelect,
[TablePaginatedFilterType.SWITCH]: () => null,
[TablePaginatedFilterType.TEXT]: Text,
}

type Props = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
@import 'src/client/style/partials';

$icon-size: 14px;

.filter-multiselect__tooltip-trigger {
height: 100%;
}

div.filter-multiselect__container {
border-radius: 2px;
border: 1px solid $ui-border;
font-size: $font-xxs;
min-width: #{3 * $spacing-xl};
padding: 0;
transition: all 0.2s ease;

div.select__control {
min-height: unset;
padding: 0 6px;

div.select__indicatorsContainer {
div.select__clearIndicator {
height: unset;
width: unset;

svg.icon {
color: rgba(0, 0, 0, 0.5);
height: $icon-size;
width: $icon-size;
}

&:hover {
background-color: unset;

svg.icon {
color: $ui-destructive;
}
}
}
}
}

div.select__dropdownIndicator {
svg {
height: $icon-size;
width: $icon-size;
}
}

&.active {
border: 1px solid $ui-accent;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import './MultiSelect.scss'
import React from 'react'

import classNames from 'classnames'

import { TablePaginatedFilterType } from 'meta/tablePaginated'
import { TooltipId } from 'meta/tooltip'

import { useAppDispatch } from 'client/store'
import { TablePaginatedActions } from 'client/store/ui/tablePaginated'
import { useTablePaginatedFilterValue } from 'client/store/ui/tablePaginated/hooks'
import Select from 'client/components/Inputs/Select'
import { TablePaginatedFilter } from 'client/components/TablePaginated/types'

import { useTooltipContent } from './hooks/useTooltipContent'

type Props = TablePaginatedFilter<TablePaginatedFilterType.MULTI_SELECT> & {
path: string
}

const MultiSelect: React.FC<Props> = (props: Props) => {
const { fieldName, label, path, options } = props

const dispatch = useAppDispatch()

const { hideTooltip, showTooltip, tooltipContent } = useTooltipContent({ fieldName, options, path })

const filterValue = useTablePaginatedFilterValue<Array<string>>(path, fieldName)

const handleChange = (value: Array<string>) => {
dispatch(
TablePaginatedActions.setFilterValue({
fieldName,
path,
value,
})
)
}

return (
<div
className="filter-multiselect__tooltip-trigger"
data-tooltip-content={tooltipContent}
data-tooltip-id={TooltipId.info}
>
<Select
classNames={{
container: classNames('filter-multiselect__container', {
active: filterValue?.length > 0,
}),
}}
isMulti
multiLabelSummaryKey="admin.role"
onChange={handleChange}
onMenuClose={showTooltip}
onMenuOpen={hideTooltip}
options={options}
placeholder={label}
value={filterValue}
/>
</div>
)
}

export default MultiSelect
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useCallback, useMemo, useState } from 'react'

import { Objects } from 'utils/objects'

import { TablePaginatedFilterType } from 'meta/tablePaginated'

import { useTablePaginatedFilterValue } from 'client/store/ui/tablePaginated/hooks'
import { TablePaginatedFilter } from 'client/components/TablePaginated/types'

type Props = {
fieldName: string
options: TablePaginatedFilter<TablePaginatedFilterType.MULTI_SELECT>['options']
path: string
}

type Returned = {
hideTooltip: () => void
showTooltip: () => void
tooltipContent: string | null
}

export const useTooltipContent = (props: Props): Returned => {
const { fieldName, options, path } = props
const filterValue = useTablePaginatedFilterValue<Array<string>>(path, fieldName)
const [canDisplayTooltip, setCanDisplayTooltip] = useState(true)

const valueToLabelMap = useMemo<Record<string, string>>(() => {
return options.reduce<Record<string, string>>((acc, { value, label }) => {
return { ...acc, [value]: label }
}, {})
}, [options])

const tooltipContent = useMemo<string | null>(() => {
if (Objects.isEmpty(filterValue)) return null
if (!canDisplayTooltip) return null

const selectedLabels = filterValue.reduce<Array<string>>((acc, value) => {
const label = valueToLabelMap[value]
if (!Objects.isEmpty(label)) acc.push(label)
return acc
}, [])

if (selectedLabels.length === 0) {
return null
}
return selectedLabels.join(', ')
}, [canDisplayTooltip, filterValue, valueToLabelMap])

const hideTooltip = useCallback(() => setCanDisplayTooltip(false), [])
const showTooltip = useCallback(() => setCanDisplayTooltip(true), [])

return {
hideTooltip,
showTooltip,
tooltipContent,
}
}
6 changes: 1 addition & 5 deletions src/client/components/TablePaginated/Filters/Text/Text.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

.table-paginated-filter-input {
display: inline-block;
height: 100%;
position: relative;

input {
Expand Down Expand Up @@ -33,11 +34,6 @@
color: $ui-destructive;
}
}

&.disabled {
opacity: 0.6;
pointer-events: none;
}
}

&.active {
Expand Down
12 changes: 5 additions & 7 deletions src/client/components/TablePaginated/Filters/Text/Text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,11 @@ const Text = (props: Props) => {
return (
<div className={classNames('table-paginated-filter-input', { active: !Objects.isEmpty(filterValue) })}>
<InputText onChange={handleChange} placeholder={label} value={filterValue ?? ''} />
<button
className={classNames('clear-button icon', { disabled: Objects.isEmpty(filterValue) })}
onClick={handleClearInput}
type="button"
>
<Icon className="icon-sub" name="remove" />
</button>
{!Objects.isEmpty(filterValue) && (
<button className="clear-button icon" onClick={handleClearInput} type="button">
<Icon className="icon-sub" name="remove" />
</button>
)}
</div>
)
}
Expand Down
1 change: 1 addition & 0 deletions src/client/components/TablePaginated/TablePaginated.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
gap: $spacing-xs;
height: $spacing-l;
padding: $spacing-xxxs;
z-index: 1;
}

.table-paginated-datagrid {
Expand Down
15 changes: 14 additions & 1 deletion src/client/components/TablePaginated/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,31 @@ export type TablePaginatedCounter = {
export type TablePaginatedEmptyListComponent = React.FC

type TablePaginatedFilterTypeMap = {
[TablePaginatedFilterType.MULTI_SELECT]: Array<string>
[TablePaginatedFilterType.SWITCH]: boolean
[TablePaginatedFilterType.TEXT]: string
}

export type TablePaginatedFilter<FilterType extends TablePaginatedFilterType> = {
type BaseTablePaginatedFilter<FilterType extends TablePaginatedFilterType> = {
defaultValue?: TablePaginatedFilterTypeMap[FilterType]
fieldName: string
hidden?: boolean
label: string
type: FilterType
}

type MultiSelectItem = {
label: string
value: string
}

type MultiSelectFilter = BaseTablePaginatedFilter<TablePaginatedFilterType.MULTI_SELECT> & {
options: Array<MultiSelectItem>
}

export type TablePaginatedFilter<FilterType extends TablePaginatedFilterType> =
FilterType extends TablePaginatedFilterType.MULTI_SELECT ? MultiSelectFilter : BaseTablePaginatedFilter<FilterType>

export type TablePaginatedSkeleton = {
baseColor: string
highlightColor: string
Expand Down
Loading
Loading