import { Component, SyntheticEvent, ReactNode, Fragment, KeyboardEvent, FC } from 'react'; import { Checkbox, TableFilterCellProps, Radio, Input, Spinner, BodyText, } from '@servicetitan/design-system'; import { IdType } from '@servicetitan/data-query'; import { CustomColumnMenuFilterSingleOpts, renderCustomColumnMenuFilter, } from '../column-menu-filters'; import { makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; export type AsyncSelectFilterDataFetcher< TV extends IdType = IdType, TO extends AsyncSelectItem = AsyncSelectItem, > = (opts: { search?: string }) => Promise<{ data: TO[] }>; export interface AsyncSelectItem { value: TV; text: string; } interface SelectorProps> { placeholder?: string; selected: TO[]; dataFetcher: AsyncSelectFilterDataFetcher; itemComponent: FC; onChange(option: TO, checked: boolean, event: SyntheticEvent): void; renderer?(item: TO): ReactNode; } interface SelectorItemProps { option: AsyncSelectItem; checked: boolean; onChange( option: AsyncSelectItem, checked: boolean, event: SyntheticEvent ): void; renderer?(item: AsyncSelectItem): ReactNode; } @observer class SelectorAsync> extends Component< SelectorProps > { @observable shownOptions: AsyncSelectItem[] = []; @observable search = ''; @observable error = false; @observable loading = false; constructor(props: SelectorProps) { super(props); makeObservable(this); } componentDidMount() { this.searchOptions().catch(); } handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Enter') { event.stopPropagation(); } }; handleSearch = (_0: SyntheticEvent, data: { value: string }) => { runInAction(() => (this.search = data.value)); this.searchOptions().catch(); }; searchOptions = async () => { runInAction(() => { this.loading = true; }); try { const { data } = await this.props.dataFetcher({ search: this.search, }); runInAction(() => { this.shownOptions = data; this.error = false; this.loading = false; }); } catch { runInAction(() => { this.error = true; this.loading = false; }); } }; render() { const selectedOptions = this.props.selected; const selected = new Set(selectedOptions.map(opt => opt.value)); const ItemComponent = this.props.itemComponent; return (
{!!selectedOptions.length && (
{selectedOptions.map(option => ( ))}
)} {this.error ? ( Unable to load options ) : this.shownOptions.length ? ( this.shownOptions .filter(opt => !selected.has(opt.value)) .map(option => ( )) ) : this.loading ? (   ) : ( No options found )} {this.loading && (
)}
); } } const SelectorItemSingle: FC = ({ option, renderer, checked, onChange }) => ( onChange(option, true, event)} className="m-b-1" /> ); const SelectorItemMultiple: FC = ({ option, renderer, checked, onChange }) => ( onChange(option, checked, event)} className="m-b-1" /> ); export interface AsyncSelectFilterOptions< TID extends IdType, TO extends AsyncSelectItem, > extends CustomColumnMenuFilterSingleOpts { dataFetcher: AsyncSelectFilterDataFetcher; placeholder?: string; multiple?: boolean; renderItem?: (item: TO) => ReactNode; } interface TableFilterCellPropsTyped extends Omit { value?: T; } /** * @deprecated use selectColumnMenuFilter instead */ export function asyncSelectColumnMenuFilter>({ dataFetcher, placeholder, multiple, renderItem, ...opts }: AsyncSelectFilterOptions) { const contains = (value: TID, options?: TO[]) => options?.some(opt => opt.value === value); const equals = (value: TID, option?: TO) => option?.value === value; const FilterCell = multiple ? ({ value, onChange }: TableFilterCellPropsTyped) => { const handleChange = ( option: TO, checked: boolean, event: SyntheticEvent ) => { const val = checked ? (value ?? []).concat(option) : (value ?? []).filter(opt => opt.value !== option.value); onChange({ value: val.length ? val : undefined, operator: val.length ? contains : '', syntheticEvent: event, }); }; return ( ); } : ({ value, onChange }: TableFilterCellPropsTyped) => { const handleChange = ( option: TO, checked: boolean, event: SyntheticEvent ) => { onChange({ value: option, operator: option ? equals : '', syntheticEvent: event, }); }; return ( ); }; return renderCustomColumnMenuFilter(FilterCell, opts); }