import { useState, useEffect, Component, Fragment, ComponentType, FC } from 'react'; import { DatePicker, TimePicker } from '@progress/kendo-react-dateinputs'; import { TableFilterCellProps } from '@servicetitan/design-system'; import { NumericTextBox, NumericTextBoxProps } from '@progress/kendo-react-inputs'; import moment from 'moment'; import { renderCustomColumnMenuFilter } from '../column-menu-filters'; // TODO: remove when Kendo fix the propType bug of DatePicker type ComponentClassWithBrokenPropTypes

= ComponentType

& { defaultProps?: any }; interface Range { min: T; max: T; } interface BaseControlProps { value?: T | null; width?: number | string; onChange?: (ev: any) => void; } type RangeControlProps = BaseControlProps & TProps; type RangeControl = Component> & { value: T | null }; const NoSSR: FC<{ children: JSX.Element }> = ({ children }) => { const [isMounted, setIsMounted] = useState(false); useEffect(() => { setIsMounted(true); }, []); return isMounted ? children : null; }; class RangeFilterCellBase extends Component { private minValueBox: RangeControl | null = null; private maxValueBox: RangeControl | null = null; constructor( props: TableFilterCellProps, private controlClass: ComponentClassWithBrokenPropTypes>, private controlProps: TProps = {} as any ) { super(props); } /** override this if additional type support needed */ inRange(current: T, values: Range) { return ( (values.min === null || values.min === undefined || current >= values.min) && (values.max === null || values.max === undefined || current <= values.max) ); } onChange = (ev: any) => { if (!this.minValueBox || !this.maxValueBox) { return; } this.props.onChange({ value: { min: this.minValueBox.value, max: this.maxValueBox.value }, operator: this.inRange, syntheticEvent: ev.syntheticEvent, }); }; render() { const filterValue = this.props.value; const Control = this.controlClass; return ( From: {/* KendoNumericTextBox uses useLayoutEffect which is incompatible with SSR */} To: {/* KendoNumericTextBox uses useLayoutEffect which is incompatible with SSR */} ); } protected setMinValueRef = (el: RangeControl | null) => { this.minValueBox = el; }; protected setMaxValueRef = (el: RangeControl | null) => { this.maxValueBox = el; }; } class NumericRangeFilterCell extends RangeFilterCellBase { constructor(props: TableFilterCellProps) { super(props, NumericTextBox); } } class CurrencyRangeFilterCell extends RangeFilterCellBase { constructor(props: TableFilterCellProps) { super(props, NumericTextBox, { format: 'c', }); } } class DateRangeFilterCell extends RangeFilterCellBase { constructor(props: TableFilterCellProps) { super(props, DatePicker); } } function getTimeString(date: Date) { return `${date.getHours()}:${date.getMinutes()}`; } class TimeRangeFilterCell extends RangeFilterCellBase { constructor(props: TableFilterCellProps) { super(props, TimePicker); } inRange(current: Date, values: Range) { return moment(getTimeString(current), 'h:mma').isBetween( moment(getTimeString(values.min || current), 'h:mma'), moment(getTimeString(values.max || current), 'h:mma'), 'minutes', '[]' ); } } export const NumericRangeColumnMenuFilter = renderCustomColumnMenuFilter(NumericRangeFilterCell); export const CurrencyRangeColumnMenuFilter = renderCustomColumnMenuFilter(CurrencyRangeFilterCell); export const DateRangeColumnMenuFilter = renderCustomColumnMenuFilter(DateRangeFilterCell); export const TimeRangeColumnMenuFilter = renderCustomColumnMenuFilter(TimeRangeFilterCell);