import { FC } from 'react'; import { observer, useLocalStore } from 'mobx-react'; import { fromPromise } from 'mobx-utils'; import { DropDownList } from '@progress/kendo-react-dropdowns'; import { TableColumnMenuProps } from '@servicetitan/design-system'; import { GridColumnMenuFilterUIProps as TableColumnMenuFilterUIProps } from '@progress/kendo-react-grid/dist/npm/interfaces/GridColumnMenuFilterUIProps'; import { IdType } from '@servicetitan/data-query'; import { TableState } from '../../table-state'; import { TableColumnMenuFilterExt } from './table-column-menu-filter-ext'; import { FilterCellExt, FilterCellExtProps } from './filter-cell-ext'; // type AcceptableOption = string | number | { value: any; text: string }; interface OptionsConfig { /* tableState is used to extract values set for multiselect */ tableState?: TableState; /* values longer than maxExtractedLength wouldn't be extracted from tableState (for sanity). Default is 65 characters */ maxExtractedLength?: number; /* set of values for multiselect */ options?: AcceptableOption[]; } interface FilterCellConfig { /* maximum number of values to show in Multiselect dropdown list at one time */ maxFoundValues?: number; /* allow user to input custom values; default: true */ allowCustomValues?: boolean; /* convert user-added Multiselect options to numbers; default: false */ handleCustomValuesAsNumber?: boolean; /* allow only "Is [not] one of" and "Is [not] empty" operators; default: false */ onlyMultiselectAndEmptyOperators?: boolean; /* Multiselect itemRender property (for very special cases) */ multiselectItemRender?: FilterCellExtProps['multiselectItemRender']; /* Multiselect tagRender property (for very special cases) */ multiselectTagRender?: FilterCellExtProps['multiselectTagRender']; /* Multiselect listNoDataRender property (for very special cases) */ multiselectListNoDataRender?: FilterCellExtProps['multiselectListNoDataRender']; } /* * StandardFilterWithMultiselect is like standard Kendo filter, but with additional operators 'Is one of' / 'Is not one of'. * Intended to be used with string fields (though it's possible to adopt for number fields as well, if someone will have a need). */ export function standardFilterWithMultiselect( optionsConfig: OptionsConfig = {}, filterCellConfig: FilterCellConfig = {} ) { const optionsConfigFull = { maxExtractedLength: 64, options: [] as string[], ...optionsConfig, }; const filterCellConfigFull = { allowCustomValues: true, maxFoundValues: 1000, ...filterCellConfig, }; // Filter UI with data fetcher const FilterUIWithValues: FC = observer(props => { const store = useLocalStore( oprops => ({ get field() { return oprops.firstFilterProps.field as keyof T; }, get itemsFromDataSource() { const dataSource = optionsConfigFull.tableState?.dataSource; return dataSource && this.field && fromPromise(dataSource.getData({})); }, get valuesfromTableState(): AcceptableOption[] { if ( !this.itemsFromDataSource || !(this.itemsFromDataSource.state === 'fulfilled') ) { return []; } return (this.itemsFromDataSource.value.data as T[]) .map(i => i[this.field]) .filter( v => typeof v === 'number' || (typeof v === 'string' && v.length > 0 && v.length <= optionsConfigFull.maxExtractedLength) ) as any[]; }, get options() { return Array.from( new Set(this.valuesfromTableState.concat(optionsConfigFull.options)) ).map(v => { switch (typeof v) { case 'string': return { value: v, text: v, textLC: v.toLowerCase(), }; case 'number': return { value: v, text: v.toString(), textLC: v.toString().toLowerCase(), }; case 'object': return { value: v.value, text: v.text, textLC: v.text.toLowerCase(), }; default: throw 'Incompatible option type.'; } }); }, }), props ); const { firstFilterProps, secondFilterProps, hideSecondFilter, logicData, logicValue, onLogicChange, } = props; return (
{!hideSecondFilter && (
)}
); }); return (props: TableColumnMenuProps) => ( ); }