Skip to content

Commit

Permalink
fix: filtering and grouping collapsed (#652)
Browse files Browse the repository at this point in the history
* fix: the issues with the checkbox field's filtering and grouping collapse

* fix: grouping collapsed error for the date field

* chore: update e2e test

* fix: checkbox field rendering

* fix: the isNot operator for filtering the date field is adapted for sqlite
  • Loading branch information
Sky-FE committed Jun 11, 2024
1 parent 813ff87 commit 2cb765a
Show file tree
Hide file tree
Showing 14 changed files with 81 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ export abstract class AbstractFilterQuery implements IFilterQueryInterface {
private shouldKeepFilterItem(value: unknown, field: IFieldInstance, operator: string): boolean {
return (
value !== null ||
field.type === FieldType.Checkbox ||
field.cellValueType === CellValueType.Boolean ||
([isEmpty.value, isNotEmpty.value] as string[]).includes(operator)
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ export class DatetimeCellValueFilterAdapter extends CellValueFilterPostgres {
const { options } = this.field;

const dateTimeRange = this.getFilterDateTimeRange(options as IDateFieldOptions, value);
builderClient.whereNotBetween(this.tableColumnRef, dateTimeRange);
builderClient
.whereNotBetween(this.tableColumnRef, dateTimeRange)
.orWhereNull(this.tableColumnRef);
return builderClient;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ export class DatetimeCellValueFilterAdapter extends CellValueFilterSqlite {
const { options } = this.field;

const dateTimeRange = this.getFilterDateTimeRange(options as IDateFieldOptions, value);
builderClient.whereNotBetween(this.tableColumnRef, dateTimeRange);
builderClient
.whereNotBetween(this.tableColumnRef, dateTimeRange)
.orWhereNull(this.tableColumnRef);
return builderClient;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const IS_NOT_SETS = [
mode: today.value,
timeZone: 'Asia/Singapore',
},
expectResultLength: 16,
expectResultLength: 22,
},
{
fieldIndex: 3,
Expand All @@ -30,7 +30,7 @@ export const IS_NOT_SETS = [
mode: tomorrow.value,
timeZone: 'Asia/Singapore',
},
expectResultLength: 16,
expectResultLength: 22,
},
{
fieldIndex: 3,
Expand All @@ -39,7 +39,7 @@ export const IS_NOT_SETS = [
mode: yesterday.value,
timeZone: 'Asia/Singapore',
},
expectResultLength: 16,
expectResultLength: 22,
},
{
fieldIndex: 3,
Expand All @@ -48,7 +48,7 @@ export const IS_NOT_SETS = [
mode: oneWeekAgo.value,
timeZone: 'Asia/Singapore',
},
expectResultLength: 16,
expectResultLength: 22,
},
{
fieldIndex: 3,
Expand All @@ -57,7 +57,7 @@ export const IS_NOT_SETS = [
mode: oneWeekFromNow.value,
timeZone: 'Asia/Singapore',
},
expectResultLength: 16,
expectResultLength: 22,
},
{
fieldIndex: 3,
Expand All @@ -66,7 +66,7 @@ export const IS_NOT_SETS = [
mode: oneMonthAgo.value,
timeZone: 'Asia/Singapore',
},
expectResultLength: 16,
expectResultLength: 22,
},
{
fieldIndex: 3,
Expand All @@ -75,7 +75,7 @@ export const IS_NOT_SETS = [
mode: oneMonthFromNow.value,
timeZone: 'Asia/Singapore',
},
expectResultLength: 16,
expectResultLength: 22,
},
{
fieldIndex: 3,
Expand All @@ -85,7 +85,7 @@ export const IS_NOT_SETS = [
numberOfDays: 1,
timeZone: 'Asia/Singapore',
},
expectResultLength: 16,
expectResultLength: 22,
},
{
fieldIndex: 3,
Expand All @@ -95,7 +95,7 @@ export const IS_NOT_SETS = [
numberOfDays: 1,
timeZone: 'Asia/Singapore',
},
expectResultLength: 16,
expectResultLength: 22,
},
{
fieldIndex: 3,
Expand All @@ -105,6 +105,6 @@ export const IS_NOT_SETS = [
exactDate: '2019-12-31T16:00:00.000Z',
timeZone: 'Asia/Singapore',
},
expectResultLength: 16,
expectResultLength: 22,
},
];
2 changes: 1 addition & 1 deletion packages/core/src/formula/typed-value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ export class TypedValue<T = any> {
) {}

toPlain(): any {
return this.value;
return this.value === false ? null : this.value;
}
}
5 changes: 5 additions & 0 deletions packages/core/src/formula/visitor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,4 +197,9 @@ describe('EvalVisitor', () => {
it('should calculate multiple link field', () => {
expect(evalFormula('{fldMultipleLink} & "x"', fieldContext, record)).toEqual(',A2x');
});

it('should return null when the value is false', () => {
const result = evaluate('1 > 2', {});
expect(result.toPlain()).toEqual(null);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const CellEditor = (props: ICellValueEditor) => {
return (
<div style={wrapStyle} className={wrapClassName}>
{isComputed ? (
<ComputedEditor cellValueString={field.cellValue2String(cellValue)} />
<ComputedEditor field={field} cellValue={cellValue} />
) : (
<CellEditorMain {...props} />
)}
Expand Down
25 changes: 22 additions & 3 deletions packages/sdk/src/components/cell-value-editor/ComputedEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,26 @@
import { CellValueType } from '@teable/core';
import { Input } from '@teable/ui-lib';
import type { Field } from '../../model';
import { CellCheckbox } from '../cell-value/cell-checkbox';

export const ComputedEditor = (props: { cellValueString?: string }) => {
const { cellValueString } = props;
interface IComputedEditorProps {
field: Field;
cellValue: unknown;
}

return <Input className="h-8 disabled:cursor-text" value={cellValueString} disabled />;
export const ComputedEditor = (props: IComputedEditorProps) => {
const { field, cellValue } = props;
const { cellValueType } = field;

if (cellValueType === CellValueType.Boolean) {
return <CellCheckbox value={cellValue as boolean | boolean[]} itemClassName="size-6" />;
}

return (
<Input
className="h-8 disabled:cursor-text"
value={field.cellValue2String(cellValue)}
disabled
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const CellCheckbox = (props: ICellCheckbox) => {
}, [value]);

return (
<div className={cn('flex', className)} style={style}>
<div className={cn('flex gap-x-1', className)} style={style}>
{innerValue?.map((val, index) => {
return (
<Checkbox key={index} className={cn('size-5', itemClassName)} checked={Boolean(val)} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const RecordEditorItem = (props: {

return (
<div className={vertical ? 'flex space-x-2' : 'space-y-2'}>
<div className={cn('w-36 flex items-top space-x-1', vertical ? 'pt-2' : 'w-full')}>
<div className={cn('w-36 flex items-top space-x-1', vertical ? 'pt-1' : 'w-full')}>
<div className="flex size-5 items-center">
<Icon />
</div>
Expand Down
8 changes: 7 additions & 1 deletion packages/sdk/src/components/filter/condition/FieldValue.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { IDateFilter, IFilterItem } from '@teable/core';
import { FieldType } from '@teable/core';
import { CellValueType, FieldType } from '@teable/core';

import { Input } from '@teable/ui-lib';
import { useContext, useMemo } from 'react';
Expand Down Expand Up @@ -135,6 +135,12 @@ function FieldValue(props: IFieldValue) {
}
return <FilterUserSelect {...props} />;
}
case FieldType.Formula: {
if (field.cellValueType === CellValueType.Boolean) {
return <FilterCheckbox value={filter.value as boolean} onChange={onSelect} />;
}
return InputComponent;
}
default:
return InputComponent;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { getValidFilterOperators } from '@teable/core';
import { useMemo, useContext } from 'react';
import { BaseSingleSelect } from '../component';
import { FilterContext } from '../context';
import { getFieldOperatorMapping } from '../utils';
import { getFieldOperatorMapping, shouldFilterByDefaultValue } from '../utils';

interface IOperatorOptions {
value: IFilterOperator;
Expand All @@ -30,9 +30,8 @@ function OperatorSelect(props: IOperatorSelectProps) {
}
return [] as IOperatorOptions[];
}, [field, labelMapping]);
const shouldDisabled = useMemo(() => {
return field?.type === 'checkbox';
}, [field]);

const shouldDisabled = useMemo(() => shouldFilterByDefaultValue(field), [field]);

return (
<BaseSingleSelect
Expand Down
17 changes: 14 additions & 3 deletions packages/sdk/src/components/filter/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { IFilter } from '@teable/core';
import { FieldType } from '@teable/core';
import { CellValueType, FieldType } from '@teable/core';
import { cloneDeep } from 'lodash';
import type { IFieldInstance } from '../../model';
import { operatorLabelMapping, fieldNumberLabelMap, EMPTY_OPERATORS } from './constant';
Expand All @@ -13,6 +13,16 @@ export const getFieldOperatorMapping = (type?: FieldType) => {
return mergedMapping;
};

export const shouldFilterByDefaultValue = (field: IFieldInstance | undefined) => {
if (!field) return false;

const { type, cellValueType } = field;
return (
type === FieldType.Checkbox ||
(type === FieldType.Formula && cellValueType === CellValueType.Boolean)
);
};

export const getFilterFieldIds = (
filter: NonNullable<IFilter>['filterSet'],
fieldMap: Record<string, IFieldInstance>
Expand All @@ -21,12 +31,13 @@ export const getFilterFieldIds = (

filter.forEach((item) => {
if (isFilterItem(item)) {
// checkbox's default value is null, but it does work
// The checkbox field and the formula field, when the cellValueType is Boolean, have a default value of null, but they can still work
const field = fieldMap[item.fieldId];
if (
item.value === 0 ||
item.value ||
EMPTY_OPERATORS.includes(item.operator) ||
fieldMap[item.fieldId]?.type === FieldType.Checkbox
shouldFilterByDefaultValue(field)
) {
filterIds.add(item.fieldId);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { is, or, and, isNot, hasNoneOf, isNotEmpty, FieldType, exactDate } from
import type { IFilter, IFilterSet, ILinkCellValue, IOperator, IUserCellValue } from '@teable/core';
import { GroupPointType } from '@teable/openapi';
import type { IGetRecordsRo, IGroupHeaderPoint, IGroupPointsVo } from '@teable/openapi';
import { zonedTimeToUtc } from 'date-fns-tz';
import { useCallback, useMemo } from 'react';
import { useFields, useView, useViewId } from '../../../hooks';
import type { GridView, IFieldInstance } from '../../../model';
import { shouldFilterByDefaultValue } from '../../filter/utils';
import { useGridCollapsedGroupStore } from '../store';

const FILTER_RELATED_FILED_TYPE_SET = new Set([
Expand All @@ -26,18 +28,21 @@ export const cellValue2FilterValue = (cellValue: unknown, field: IFieldInstance)

export const generateFilterItem = (field: IFieldInstance, value: unknown) => {
let operator: IOperator = isNot.value;
const { id: fieldId, type, isMultipleCellValue } = field;
const { id: fieldId, type, isMultipleCellValue, options } = field;

if (type === FieldType.Checkbox) {
if (shouldFilterByDefaultValue(field)) {
operator = is.value;
value = !value || null;
} else if (value == null) {
operator = isNotEmpty.value;
} else if (type === FieldType.Date) {
const timeZone =
options?.formatting?.timeZone ?? Intl.DateTimeFormat().resolvedOptions().timeZone;
const dateStr = zonedTimeToUtc(value as string, timeZone).toISOString();
value = {
exactDate: value,
exactDate: dateStr,
mode: exactDate.value,
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
timeZone,
};
} else if (FILTER_RELATED_FILED_TYPE_SET.has(type) && isMultipleCellValue) {
operator = hasNoneOf.value;
Expand Down

0 comments on commit 2cb765a

Please sign in to comment.