import { ReactElement, ReactNode, FC, ChangeEvent } from 'react'; import { runInAction } from 'mobx'; import { observer, useLocalStore } from 'mobx-react'; import { multiselectOperators, isMultiselectOperator } from './multiselect-operators'; import { DropDownList, DropDownListChangeEvent, MultiSelect, MultiSelectChangeEvent, MultiSelectFilterChangeEvent, ListItemProps, MultiSelectTagData, } from '@progress/kendo-react-dropdowns'; import { GridColumnMenuFilterCellProps as TableColumnMenuFilterCellProps } from '@progress/kendo-react-grid/dist/npm/columnMenu/GridColumnMenuFilterCell'; import { IsUnaryFilter, cellOperatorChange, } from '@progress/kendo-react-grid/dist/npm/filterCommon'; import { Icon, Tooltip } from '@servicetitan/design-system'; import * as Styles from './filter-cell-ext.module.css'; import classNames from 'classnames'; // interface ValueWithLowerCase { value: any; text: string; textLC: string; } export interface FilterCellExtProps extends TableColumnMenuFilterCellProps { options: ValueWithLowerCase[]; maxFoundValues?: number; allowCustomValues?: boolean; onlyMultiselectAndEmptyOperators?: boolean; handleCustomValuesAsNumber?: boolean; multiselectItemRender?(li: ReactElement, itemProps: ListItemProps): ReactNode; multiselectTagRender?( tagData: MultiSelectTagData, li: ReactElement ): ReactElement; multiselectListNoDataRender?(element: ReactElement): ReactNode; } // like standard filter cell (rendered for string type), but with support of multiselect operators export const FilterCellExt: FC = observer(props => { const store = useLocalStore( oprops => ({ get operators() { return multiselectOperators.concat( oprops.onlyMultiselectAndEmptyOperators ? oprops.operators.filter( o => o.operator === 'isempty' || o.operator === 'isnotempty' ) : oprops.operators ); }, get operatorValue() { return this.operators.find(item => item.operator === oprops.operator); }, searchQuery: '', filterChange(event: MultiSelectFilterChangeEvent) { runInAction(() => (this.searchQuery = event.filter.value)); }, get filteredValuesSet() { const queryLC = this.searchQuery.toLowerCase(); return oprops.options.filter(v => v.textLC.includes(queryLC)); }, get onlyFirstValues() { return oprops.maxFoundValues && oprops.maxFoundValues < this.filteredValuesSet.length ? oprops.maxFoundValues : false; }, get filteredVisibleValues() { return this.onlyFirstValues ? this.filteredValuesSet.slice(0, this.onlyFirstValues) : this.filteredValuesSet; }, get valueTextMap() { return new Map(oprops.options.map(o => [o.value, o.text])); }, get multiselectValue() { return Array.isArray(oprops.value) ? oprops.value.map(v => ({ value: v, text: this.valueTextMap.get(v) ?? v.toString(), })) : []; }, }), props ); const { operator = '', onChange, allowCustomValues, multiselectItemRender, multiselectTagRender, multiselectListNoDataRender, } = props; const handleInputChange = (e: ChangeEvent) => onChange({ operator, value: e.target.value, syntheticEvent: e, }); const handleMultiselectChange = (e: MultiSelectChangeEvent) => { const newValue = e.value.map( (v: any) => v.value ?? (props.handleCustomValuesAsNumber ? +v.text : v.text) ); onChange({ operator, value: newValue.length ? newValue : '', // for Kendo to handle empty array as an empty value TODO: check if there is another way syntheticEvent: e.syntheticEvent, }); }; // conversions for better compatibility when switching plain/array operators const handleOperatorChange = (event: DropDownListChangeEvent) => { const newValue = isMultiselectOperator(operator) === isMultiselectOperator(event.target.value.operator) ? props.value : ''; cellOperatorChange(event, newValue, onChange); }; const allowCustomAdd = (allowCustomValues && store.searchQuery && (!props.handleCustomValuesAsNumber || !isNaN(+store.searchQuery))) || false; return (
{(typeof operator !== 'string' || !IsUnaryFilter(operator)) && (isMultiselectOperator(operator) ? ( } footer={ allowCustomAdd ? (
to create filter
) : ( store.onlyFirstValues && (
First {store.onlyFirstValues} values are shown.
Type to search for other values.
) ) } itemRender={multiselectItemRender} tagRender={multiselectTagRender ?? defaultMultiselectTagRender} listNoDataRender={multiselectListNoDataRender} /> ) : ( ))}
); }); const defaultMultiselectTagRender = ( tagData: MultiSelectTagData, li: ReactElement ): ReactElement => { return ( {li} ); };