165 lines
4.8 KiB
TypeScript
165 lines
4.8 KiB
TypeScript
/* eslint-disable react-refresh/only-export-components */
|
|
import { type BaseGridColumn, type GridCell, GridCellKind } from '@glideapps/glide-data-grid';
|
|
import { ActionIcon, Select, Stack, TextInput } from '@mantine/core';
|
|
import { useDebouncedValue } from '@mantine/hooks';
|
|
import { IconX } from '@tabler/icons-react';
|
|
import { type ReactNode, useEffect, useState } from 'react';
|
|
|
|
import type { FilterOption, FilterOptionOperator, GridlerStoreState } from './GridlerStore';
|
|
|
|
export type GridCellLoose = {
|
|
kind: GridCellKind | string;
|
|
} & Omit<GridCell, 'allowOverlay' | 'kind'>;
|
|
|
|
export interface GridlerColumn extends Partial<BaseGridColumn> {
|
|
Cell?: (
|
|
row: any,
|
|
col: number,
|
|
colIndx: string,
|
|
value: any,
|
|
storeState: GridlerStoreState
|
|
) => Partial<GridCellLoose>;
|
|
defaultIcon?: string;
|
|
disableFilter?: boolean;
|
|
disableMove?: boolean;
|
|
disableResize?: boolean;
|
|
disableSort?: boolean;
|
|
getMenuItems?: (
|
|
id: string,
|
|
storeState: any,
|
|
row?: any,
|
|
col?: GridlerColumn,
|
|
defaultItems?: Array<any>
|
|
) => Array<any>;
|
|
id: string;
|
|
maxWidth?: number;
|
|
minWidth?: number;
|
|
tooltip?: ((buffer: any, row: number, col: number) => ReactNode) | string;
|
|
width?: number;
|
|
}
|
|
|
|
export const FilterOperators: Array<{ label: string; value: FilterOptionOperator }> = [
|
|
{ label: 'Contains', value: 'contains' },
|
|
{ label: 'Equal', value: 'eq' },
|
|
{ label: 'Not Equal', value: 'neq' },
|
|
{ label: 'Greater Than', value: 'gt' },
|
|
{ label: 'Greater Than or Equal', value: 'gte' },
|
|
{ label: 'Less Than', value: 'lt' },
|
|
{ label: 'Less Than or Equal', value: 'lte' },
|
|
];
|
|
|
|
export interface ColumnFilterSetProps {
|
|
column: GridlerColumn;
|
|
options?: Partial<FilterOption>;
|
|
storeState: GridlerStoreState;
|
|
}
|
|
|
|
export type GridlerColumns = Array<GridlerColumn>;
|
|
|
|
export const ColumnFilterInput = (props: ColumnFilterSetProps) => {
|
|
const filterIndex =
|
|
props.storeState?.colFilters?.findIndex((f) => f.id === props.column.id) ?? -1;
|
|
const filter = props.storeState?.colFilters?.[filterIndex] ?? { id: props.column.id, value: '' };
|
|
|
|
const [filterValue, setFilterValue] = useState<string>(filter?.value ?? '');
|
|
const [defferedFilterValue] = useDebouncedValue(filterValue, 900);
|
|
|
|
useEffect(() => {
|
|
props.storeState.setStateFN('colFilters', (state) => {
|
|
const idx = state?.findIndex((f) => f.id === props.column.id) ?? -1;
|
|
|
|
const filters = state ?? [];
|
|
if (idx >= 0) {
|
|
filters[idx] = {
|
|
...filters[idx],
|
|
...props.options,
|
|
id: props.column.id,
|
|
value: defferedFilterValue,
|
|
};
|
|
} else {
|
|
filters.push({
|
|
operator: 'contains',
|
|
...props.options,
|
|
id: props.column.id,
|
|
value: defferedFilterValue,
|
|
});
|
|
}
|
|
|
|
return filters;
|
|
});
|
|
}, [defferedFilterValue, props.column.id, props.options, props.storeState]);
|
|
|
|
return (
|
|
<TextInput
|
|
onChange={(e) => setFilterValue(e.target.value)}
|
|
rightSection={
|
|
<ActionIcon color="gray" onClick={() => setFilterValue('')} variant="filled">
|
|
<IconX color="red" />
|
|
</ActionIcon>
|
|
}
|
|
value={filterValue ?? filter?.value}
|
|
/>
|
|
);
|
|
};
|
|
|
|
export const ColumnFilterInputOperator = (props: ColumnFilterSetProps) => {
|
|
const filterIndex =
|
|
props.storeState?.colFilters?.findIndex((f) => f.id === props.column.id) ?? -1;
|
|
const filter = props.storeState?.colFilters?.[filterIndex] ?? {
|
|
id: props.column.id,
|
|
operator: 'contains',
|
|
};
|
|
|
|
const [filterValue, setFilterValue] = useState<FilterOptionOperator>(
|
|
filter?.operator ?? 'contains'
|
|
);
|
|
const [defferedFilterValue] = useDebouncedValue(filterValue, 900);
|
|
|
|
useEffect(() => {
|
|
props.storeState.setStateFN('colFilters', (state) => {
|
|
const idx = state?.findIndex((f) => f.id === props.column.id) ?? -1;
|
|
|
|
const filters = state ?? [];
|
|
if (idx >= 0) {
|
|
filters[idx] = {
|
|
...filters[idx],
|
|
...props.options,
|
|
id: props.column.id,
|
|
operator: defferedFilterValue,
|
|
};
|
|
} else {
|
|
filters.push({
|
|
...props.options,
|
|
id: props.column.id,
|
|
operator: defferedFilterValue,
|
|
value: '',
|
|
});
|
|
}
|
|
|
|
return filters;
|
|
});
|
|
}, [defferedFilterValue, props.column.id, props.options, props.storeState]);
|
|
|
|
return (
|
|
<Select
|
|
comboboxProps={{ withinPortal: false }}
|
|
data={FilterOperators}
|
|
maxDropdownHeight={150}
|
|
onChange={(value) => setFilterValue(value as any)}
|
|
placeholder="Operator"
|
|
searchable
|
|
value={filterValue ?? filter?.operator}
|
|
withScrollArea
|
|
/>
|
|
);
|
|
};
|
|
|
|
export const ColumnFilterSet = (props: ColumnFilterSetProps) => {
|
|
return (
|
|
<Stack onClick={(e) => e.stopPropagation()}>
|
|
<ColumnFilterInputOperator {...props} />
|
|
<ColumnFilterInput {...props} />
|
|
</Stack>
|
|
);
|
|
};
|