-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* filters refactor * Clean up arrayWrap
- Loading branch information
Showing
5 changed files
with
315 additions
and
389 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
105 changes: 105 additions & 0 deletions
105
grai-frontend/src/components/filters/FilterRowValue.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import React from "react" | ||
import { CheckBox, CheckBoxOutlineBlank } from "@mui/icons-material" | ||
import { | ||
Autocomplete, | ||
AutocompleteChangeReason, | ||
Checkbox, | ||
TextField, | ||
} from "@mui/material" | ||
import arrayFirst from "helpers/arrayFirst" | ||
import arrayWrap from "helpers/arrayWrap" | ||
import notEmpty from "helpers/notEmpty" | ||
import { Filter, OperationOption, Operator } from "./filters" | ||
|
||
const icon = <CheckBoxOutlineBlank fontSize="small" /> | ||
const checkedIcon = <CheckBox fontSize="small" /> | ||
|
||
type FilterRowValueProps = { | ||
operator: Operator | null | ||
filter: Filter | ||
onChange: (filter: Filter) => void | ||
} | ||
|
||
const FilterRowValue: React.FC<FilterRowValueProps> = ({ | ||
operator, | ||
filter, | ||
onChange, | ||
}) => { | ||
const handleValueChange = ( | ||
event: React.SyntheticEvent<Element, Event>, | ||
newValue: | ||
| null | ||
| string | ||
| OperationOption | ||
| (null | string | OperationOption)[], | ||
reason: AutocompleteChangeReason, | ||
) => | ||
onChange({ | ||
...filter, | ||
value: Array.isArray(newValue) | ||
? newValue | ||
.map(option => | ||
typeof option === "string" ? option : option?.value, | ||
) | ||
.filter(notEmpty) | ||
: (typeof newValue === "string" ? newValue : newValue?.value) ?? null, | ||
}) | ||
|
||
if (!operator?.options) | ||
return ( | ||
<TextField | ||
fullWidth | ||
disabled={!operator} | ||
value={filter.value ?? ""} | ||
onChange={event => onChange({ ...filter, value: event.target.value })} | ||
inputProps={{ | ||
"data-testid": "value", | ||
}} | ||
/> | ||
) | ||
|
||
if (operator.multiple) | ||
return ( | ||
<Autocomplete<string | OperationOption, true> | ||
multiple | ||
openOnFocus | ||
autoSelect | ||
limitTags={1} | ||
options={operator.options} | ||
value={operator.options.filter(option => | ||
arrayWrap(filter.value).includes( | ||
typeof option === "string" ? option : option?.value, | ||
), | ||
)} | ||
onChange={handleValueChange} | ||
renderInput={params => <TextField {...params} />} | ||
renderOption={(props, option, { selected }) => ( | ||
<li {...props}> | ||
<Checkbox | ||
icon={icon} | ||
checkedIcon={checkedIcon} | ||
style={{ marginRight: 8 }} | ||
checked={selected} | ||
/> | ||
{typeof option === "string" ? option : option?.label} | ||
</li> | ||
)} | ||
data-testid="autocomplete-value" | ||
/> | ||
) | ||
|
||
return ( | ||
<Autocomplete<string | OperationOption, false> | ||
openOnFocus | ||
autoSelect | ||
disabled={!operator} | ||
options={operator.options} | ||
value={arrayFirst(filter.value)} | ||
onChange={handleValueChange} | ||
renderInput={params => <TextField {...params} />} | ||
data-testid="autocomplete-value" | ||
/> | ||
) | ||
} | ||
|
||
export default FilterRowValue |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
export type Filter = { | ||
type: string | null | ||
field: string | null | ||
operator: string | null | ||
value: string | string[] | null | ||
} | ||
|
||
export interface OperationOption { | ||
value: string | ||
label: string | ||
} | ||
|
||
export type Operator = { | ||
value: string | ||
label: string | ||
shortLabel?: string | ||
options?: (string | OperationOption)[] | ||
multiple?: boolean | ||
} | ||
|
||
export type Field = { | ||
value: string | ||
label: string | ||
disabled?: boolean | ||
operators: Operator[] | ||
} | ||
|
||
export type Property = { | ||
value: string | ||
label: string | ||
disabled?: boolean | ||
fields: Field[] | ||
} | ||
|
||
export interface Source { | ||
id: string | ||
name: string | ||
} | ||
|
||
export const defaultFilter: Filter = { | ||
type: "table", | ||
field: null, | ||
operator: null, | ||
value: null, | ||
} | ||
|
||
const nameField: Field = { | ||
value: "name", | ||
label: "Name", | ||
operators: [ | ||
{ | ||
value: "equals", | ||
label: "Equals", | ||
shortLabel: "=", | ||
}, | ||
{ | ||
value: "contains", | ||
label: "Contains", | ||
shortLabel: "*a*", | ||
}, | ||
{ | ||
value: "starts-with", | ||
label: "Starts With", | ||
shortLabel: "a*", | ||
}, | ||
{ | ||
value: "ends-with", | ||
label: "Ends With", | ||
shortLabel: "*a", | ||
}, | ||
{ | ||
value: "not-equals", | ||
label: "Not Equals", | ||
shortLabel: "!=", | ||
}, | ||
{ | ||
value: "not-contains", | ||
label: "Not Contains", | ||
shortLabel: "!*a*", | ||
}, | ||
], | ||
} | ||
|
||
const namespaceField = (namespaces: string[]): Field => ({ | ||
value: "namespace", | ||
label: "Namespace", | ||
operators: [ | ||
{ | ||
value: "equals", | ||
label: "Equals", | ||
options: namespaces, | ||
}, | ||
{ | ||
value: "in", | ||
label: "In", | ||
options: namespaces, | ||
multiple: true, | ||
}, | ||
], | ||
}) | ||
|
||
const sourceField = (sources: Source[]): Field => ({ | ||
value: "data-source", | ||
label: "Data Source", | ||
operators: [ | ||
{ | ||
value: "in", | ||
label: "In", | ||
options: sources.map(source => ({ | ||
value: source.id, | ||
label: source.name, | ||
})), | ||
multiple: true, | ||
}, | ||
{ | ||
value: "not-in", | ||
label: "Not In", | ||
options: sources.map(source => ({ | ||
value: source.id, | ||
label: source.name, | ||
})), | ||
multiple: true, | ||
}, | ||
], | ||
}) | ||
|
||
const tagField = (tags: string[]): Field => ({ | ||
value: "tag", | ||
label: "Tag", | ||
operators: [ | ||
{ | ||
value: "contains", | ||
label: "Contains", | ||
options: tags, | ||
}, | ||
{ | ||
value: "not-contains", | ||
label: "Doesn't Contain", | ||
options: tags, | ||
}, | ||
], | ||
}) | ||
|
||
export const getProperties = ( | ||
namespaces: string[], | ||
tags: string[], | ||
sources: Source[], | ||
): Property[] => [ | ||
{ | ||
value: "table", | ||
label: "Default", | ||
fields: [ | ||
nameField, | ||
namespaceField(namespaces), | ||
sourceField(sources), | ||
tagField(tags), | ||
], | ||
}, | ||
{ | ||
value: "parent", | ||
label: "Has Parent", | ||
disabled: true, | ||
fields: [], | ||
}, | ||
{ | ||
value: "no-parent", | ||
label: "No Parent", | ||
disabled: true, | ||
fields: [], | ||
}, | ||
{ | ||
value: "child", | ||
label: "Has Child", | ||
disabled: true, | ||
fields: [], | ||
}, | ||
{ | ||
value: "no-child", | ||
label: "No Child", | ||
disabled: true, | ||
fields: [], | ||
}, | ||
{ | ||
value: "ancestor", | ||
label: "Has Ancestor", | ||
fields: [tagField(tags)], | ||
}, | ||
{ | ||
value: "no-ancestor", | ||
label: "No Ancestor", | ||
fields: [tagField(tags)], | ||
}, | ||
{ | ||
value: "descendant", | ||
label: "Has Descendant", | ||
fields: [tagField(tags)], | ||
}, | ||
{ | ||
value: "no-descendant", | ||
label: "No Descendant", | ||
fields: [tagField(tags)], | ||
}, | ||
] |
Oops, something went wrong.