oranguru/src/Gridler/components/Column.tsx
2025-10-22 17:06:31 +02:00

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>
);
};