Repository: twentyhq/twenty
Pull request #11717: Automatically Apply Values on Filtered Views
Author: login: guillim
Created: 2025-04-24T11:53:49Z | Merged: 2025-04-24T17:02:03Z

Issue : When I create a task in the "Assigned to me" task view, it will disappear from the view because the Assignee field isn't automatically populated.

Solution :
We created a "buildRecordInputFromFilters" funciron that will convert filtered into their corresponding values for the input.


Fixes https://github.com/twentyhq/core-team-issues/issues/708


Diff:
diff --git a/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilter.ts
index 353dad717a63f..f82b62c1b2d44 100644
--- a/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilter.ts
+++ b/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilter.ts
@@ -1,4 +1,5 @@
 import { FilterableFieldType } from '@/object-record/record-filter/types/FilterableFieldType';
+import { FILTER_OPERANDS_MAP } from '@/object-record/record-filter/utils/getRecordFilterOperands';
 import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
 
 export type RecordFilter = {
@@ -14,3 +15,6 @@ export type RecordFilter = {
   label: string;
   subFieldName?: string | null | undefined;
 };
+
+export type RecordFilterToRecordInputOperand<T extends FilterableFieldType> =
+  (typeof FILTER_OPERANDS_MAP)[T][number];
diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts
index a94c9a5cc6594..33ff418ae0cc4 100644
--- a/packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts
+++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts
@@ -7,17 +7,116 @@ export type GetRecordFilterOperandsParams = {
   subFieldName?: string | null | undefined;
 };
 
+const emptyOperands = [
+  RecordFilterOperand.IsEmpty,
+  RecordFilterOperand.IsNotEmpty,
+] as const;
+
+const relationOperands = [
+  RecordFilterOperand.Is,
+  RecordFilterOperand.IsNot,
+] as const;
+
+type FilterOperandMap = {
+  [K in FilterableFieldType]: readonly RecordFilterOperand[];
+};
+
+export const FILTER_OPERANDS_MAP = {
+  TEXT: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  EMAILS: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  FULL_NAME: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  ADDRESS: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  LINKS: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  PHONES: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  CURRENCY: [
+    RecordFilterOperand.GreaterThan,
+    RecordFilterOperand.LessThan,
+    ...emptyOperands,
+  ],
+  NUMBER: [
+    RecordFilterOperand.GreaterThan,
+    RecordFilterOperand.LessThan,
+    ...emptyOperands,
+  ],
+  RAW_JSON: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  DATE_TIME: [
+    RecordFilterOperand.Is,
+    RecordFilterOperand.IsRelative,
+    RecordFilterOperand.IsInPast,
+    RecordFilterOperand.IsInFuture,
+    RecordFilterOperand.IsToday,
+    RecordFilterOperand.IsBefore,
+    RecordFilterOperand.IsAfter,
+    ...emptyOperands,
+  ],
+  DATE: [
+    RecordFilterOperand.Is,
+    RecordFilterOperand.IsRelative,
+    RecordFilterOperand.IsInPast,
+    RecordFilterOperand.IsInFuture,
+    RecordFilterOperand.IsToday,
+    RecordFilterOperand.IsBefore,
+    RecordFilterOperand.IsAfter,
+    ...emptyOperands,
+  ],
+  RATING: [
+    RecordFilterOperand.Is,
+    RecordFilterOperand.GreaterThan,
+    RecordFilterOperand.LessThan,
+    ...emptyOperands,
+  ],
+  RELATION: [...relationOperands, ...emptyOperands],
+  MULTI_SELECT: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  SELECT: [RecordFilterOperand.Is, RecordFilterOperand.IsNot, ...emptyOperands],
+  ACTOR: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  ARRAY: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  BOOLEAN: [RecordFilterOperand.Is],
+} as const satisfies FilterOperandMap;
+
 export const getRecordFilterOperands = ({
   filterType,
   subFieldName,
-}: GetRecordFilterOperandsParams): RecordFilterOperand[] => {
-  const emptyOperands = [
-    RecordFilterOperand.IsEmpty,
-    RecordFilterOperand.IsNotEmpty,
-  ];
-
-  const relationOperands = [RecordFilterOperand.Is, RecordFilterOperand.IsNot];
-
+}: GetRecordFilterOperandsParams) => {
   switch (filterType) {
     case 'TEXT':
     case 'EMAILS':
@@ -25,57 +124,23 @@ export const getRecordFilterOperands = ({
     case 'ADDRESS':
     case 'LINKS':
     case 'PHONES':
-      return [
-        RecordFilterOperand.Contains,
-        RecordFilterOperand.DoesNotContain,
-        ...emptyOperands,
-      ];
+      return FILTER_OPERANDS_MAP.TEXT;
     case 'CURRENCY':
     case 'NUMBER':
-      return [
-        RecordFilterOperand.GreaterThan,
-        RecordFilterOperand.LessThan,
-        ...emptyOperands,
-      ];
+      return FILTER_OPERANDS_MAP.NUMBER;
     case 'RAW_JSON':
-      return [
-        RecordFilterOperand.Contains,
-        RecordFilterOperand.DoesNotContain,
-        ...emptyOperands,
-      ];
+      return FILTER_OPERANDS_MAP.RAW_JSON;
     case 'DATE_TIME':
     case 'DATE':
-      return [
-        RecordFilterOperand.Is,
-        RecordFilterOperand.IsRelative,
-        RecordFilterOperand.IsInPast,
-        RecordFilterOperand.IsInFuture,
-        RecordFilterOperand.IsToday,
-        RecordFilterOperand.IsBefore,
-        RecordFilterOperand.IsAfter,
-        ...emptyOperands,
-      ];
+      return FILTER_OPERANDS_MAP.DATE_TIME;
     case 'RATING':
-      return [
-        RecordFilterOperand.Is,
-        RecordFilterOperand.GreaterThan,
-        RecordFilterOperand.LessThan,
-        ...emptyOperands,
-      ];
+      return FILTER_OPERANDS_MAP.RATING;
     case 'RELATION':
-      return [...relationOperands, ...emptyOperands];
+      return FILTER_OPERANDS_MAP.RELATION;
     case 'MULTI_SELECT':
-      return [
-        RecordFilterOperand.Contains,
-        RecordFilterOperand.DoesNotContain,
-        ...emptyOperands,
-      ];
+      return FILTER_OPERANDS_MAP.MULTI_SELECT;
     case 'SELECT':
-      return [
-        RecordFilterOperand.Is,
-        RecordFilterOperand.IsNot,
-        ...emptyOperands,
-      ];
+      return FILTER_OPERANDS_MAP.SELECT;
     case 'ACTOR': {
       if (isFilterOnActorSourceSubField(subFieldName)) {
         return [
@@ -85,20 +150,12 @@ export const getRecordFilterOperands = ({
         ];
       }
 
-      return [
-        RecordFilterOperand.Contains,
-        RecordFilterOperand.DoesNotContain,
-        ...emptyOperands,
-      ];
+      return FILTER_OPERANDS_MAP.ACTOR;
     }
     case 'ARRAY':
-      return [
-        RecordFilterOperand.Contains,
-        RecordFilterOperand.DoesNotContain,
-        ...emptyOperands,
-      ];
+      return FILTER_OPERANDS_MAP.ARRAY;
     case 'BOOLEAN':
-      return [RecordFilterOperand.Is];
+      return FILTER_OPERANDS_MAP.BOOLEAN;
     default:
       return [];
   }
diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useBuildRecordInputFromFilters.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useBuildRecordInputFromFilters.ts
new file mode 100644
index 0000000000000..288805f51fc05
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/useBuildRecordInputFromFilters.ts
@@ -0,0 +1,58 @@
+import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
+import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
+import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
+import { buildValueFromFilter } from '@/object-record/record-table/utils/buildRecordInputFromFilter';
+
+import { ObjectRecord } from '@/object-record/types/ObjectRecord';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+import { useRecoilValue } from 'recoil';
+import { isDefined } from 'twenty-shared/utils';
+
+export const useBuildRecordInputFromFilters = ({
+  objectMetadataItem,
+}: {
+  objectMetadataItem: ObjectMetadataItem;
+}) => {
+  // we might need to build a recoil callback for better performance
+  const currentRecordFilters = useRecoilComponentValueV2(
+    currentRecordFiltersComponentState,
+  );
+
+  const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
+
+  const buildRecordInputFromFilters = (): Partial<ObjectRecord> => {
+    const recordInput: Partial<ObjectRecord> = {};
+
+    currentRecordFilters.forEach((filter) => {
+      const fieldMetadataItem = objectMetadataItem.fields.find(
+        (field) => field.id === filter.fieldMetadataId,
+      );
+
+      if (!isDefined(fieldMetadataItem)) {
+        return;
+      }
+      if (fieldMetadataItem.type === 'RELATION') {
+        const value = buildValueFromFilter({
+          filter,
+          options: fieldMetadataItem.options ?? undefined,
+          relationType: fieldMetadataItem.relationDefinition?.direction,
+          currentWorkspaceMember: currentWorkspaceMember ?? undefined,
+          label: filter.label,
+        });
+        if (!isDefined(value)) {
+          return;
+        }
+        recordInput[`${fieldMetadataItem.name}Id`] = value;
+      } else {
+        recordInput[fieldMetadataItem.name] = buildValueFromFilter({
+          filter,
+          options: fieldMetadataItem.options ?? undefined,
+        });
+      }
+    });
+
+    return recordInput;
+  };
+
+  return { buildRecordInputFromFilters };
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewIndexRecord.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewIndexRecord.ts
index 1637a47715218..605e7a50e2fca 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewIndexRecord.ts
+++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewIndexRecord.ts
@@ -2,6 +2,7 @@ import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordIn
 import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
 import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
 import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
+import { useBuildRecordInputFromFilters } from '@/object-record/record-table/hooks/useBuildRecordInputFromFilters';
 import { useRecordTitleCell } from '@/object-record/record-title-cell/hooks/useRecordTitleCell';
 import { RecordTitleCellContainerType } from '@/object-record/record-title-cell/types/RecordTitleCellContainerType';
 import { ObjectRecord } from '@/object-record/types/ObjectRecord';
@@ -27,17 +28,25 @@ export const useCreateNewIndexRecord = ({
 
   const { openRecordTitleCell } = useRecordTitleCell();
 
+  const { buildRecordInputFromFilters } = useBuildRecordInputFromFilters({
+    objectMetadataItem,
+  });
+
   const createNewIndexRecord = useRecoilCallback(
     ({ snapshot }) =>
       async (recordInput?: Partial<ObjectRecord>) => {
         const recordId = v4();
+        const recordInputFromFilters = buildRecordInputFromFilters();
 
         const recordIndexOpenRecordIn = snapshot
           .getLoadable(recordIndexOpenRecordInState)
           .getValue();
 
-        await createOneRecord({ id: recordId, ...recordInput });
-
+        await createOneRecord({
+          id: recordId,
+          ...recordInputFromFilters,
+          ...recordInput,
+        });
         if (recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL) {
           openRecordInCommandMenu({
             recordId,
@@ -58,6 +67,7 @@ export const useCreateNewIndexRecord = ({
         }
       },
     [
+      buildRecordInputFromFilters,
       createOneRecord,
       navigate,
       objectMetadataItem.labelIdentifierFieldMetadataId,
diff --git a/packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.spec.ts b/packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.spec.ts
new file mode 100644
index 0000000000000..afdceb433def3
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.spec.ts
@@ -0,0 +1,500 @@
+import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
+import { FilterableFieldType } from '@/object-record/record-filter/types/FilterableFieldType';
+import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
+import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
+import { ColorScheme } from '@/workspace-member/types/WorkspaceMember';
+import { RelationDefinitionType } from '~/generated-metadata/graphql';
+import { buildValueFromFilter } from './buildRecordInputFromFilter';
+
+// TODO: fix the dates, and test the not supported types
+const mockDate = new Date('2024-03-20T12:00:00Z');
+
+beforeAll(() => {
+  jest.useFakeTimers();
+  jest.setSystemTime(mockDate);
+});
+
+afterAll(() => {
+  jest.useRealTimers();
+});
+
+describe('buildValueFromFilter', () => {
+  const createTestFilter = (
+    operand: ViewFilterOperand,
+    value: string,
+    type: FilterableFieldType,
+  ): RecordFilter => ({
+    id: 'test-id',
+    fieldMetadataId: 'test-field-id',
+    value,
+    displayValue: value,
+    type,
+    operand,
+    label: 'Test Label',
+  });
+
+  describe('TEXT field type', () => {
+    const testCases = [
+      {
+        operand: ViewFilterOperand.Contains,
+        value: 'test',
+        expected: 'test',
+      },
+      {
+        operand: ViewFilterOperand.DoesNotContain,
+        value: 'test',
+        expected: undefined,
+      },
+      {
+        operand: ViewFilterOperand.IsNotEmpty,
+        value: 'test',
+        expected: 'test',
+      },
+      {
+        operand: ViewFilterOperand.IsEmpty,
+        value: 'test',
+        expected: undefined,
+      },
+    ];
+
+    it.each(testCases)(
+      'should handle $operand with value "$value"',
+      ({ operand, value, expected }) => {
+        const filter = createTestFilter(operand, value, 'TEXT');
+        expect(buildValueFromFilter({ filter })).toBe(expected);
+      },
+    );
+  });
+
+  describe('DATE_TIME field type', () => {
+    const testCases = [
+      {
+        operand: ViewFilterOperand.Is,
+        value: '2024-03-20T12:00:00Z',
+        expected: mockDate,
+      },
+      {
+        operand: ViewFilterOperand.IsAfter,
+        value: '2024-03-20T12:00:00Z',
+        expected: mockDate,
+      },
+      {
+        operand: ViewFilterOperand.IsBefore,
+        value: '2024-03-20T12:00:00Z',
+        expected: mockDate,
+      },
+      {
+        operand: ViewFilterOperand.IsInPast,
+        value: '2024-03-20T12:00:00Z',
+        expected: mockDate,
+      },
+      {
+        operand: ViewFilterOperand.IsInFuture,
+        value: '2024-03-20T12:00:00Z',
+        expected: mockDate,
+      },
+      {
+        operand: ViewFilterOperand.IsToday,
+        value: '',
+        expected: mockDate,
+      },
+      {
+        operand: ViewFilterOperand.IsRelative,
+        value: '',
+        expected: mockDate,
+      },
+      {
+        operand: ViewFilterOperand.IsEmpty,
+        value: '',
+        expected: undefined,
+      },
+      {
+        operand: ViewFilterOperand.IsNotEmpty,
+        value: '2024-03-20T12:00:00Z',
+        expected: mockDate,
+      },
+    ];
+
+    it.each(testCases)(
+      'should handle $operand with value "$value"',
+      ({ operand, value, expected }) => {
+        const filter = createTestFilter(operand, value, 'DATE_TIME');
+        const result = buildValueFromFilter({ filter });
+        if (expected instanceof Date) {
+          expect(result).toBeInstanceOf(Date);
+          expect(result).toEqual(expected);
+        } else {
+          expect(result).toBe(expected);
+        }
+      },
+    );
+  });
+
+  describe('NUMBER field type', () => {
+    const testCases = [
+      {
+        operand: ViewFilterOperand.GreaterThan,
+        value: '5',
+        expected: 6,
+      },
+      {
+        operand: ViewFilterOperand.LessThan,
+        value: '5',
+        expected: 4,
+      },
+      {
+        operand: ViewFilterOperand.IsNotEmpty,
+        value: '5',
+        expected: 5,
+      },
+      {
+        operand: ViewFilterOperand.IsEmpty,
+        value: '5',
+        expected: undefined,
+      },
+    ];
+
+    it.each(testCases)(
+      'should handle $operand with value "$value"',
+      ({ operand, value, expected }) => {
+        const filter = createTestFilter(operand, value, 'NUMBER');
+        expect(buildValueFromFilter({ filter })).toBe(expected);
+      },
+    );
+  });
+
+  describe('BOOLEAN field type', () => {
+    const testCases = [
+      {
+        operand: ViewFilterOperand.Is,
+        value: 'true',
+        expected: true,
+      },
+      {
+        operand: ViewFilterOperand.Is,
+        value: 'false',
+        expected: false,
+      },
+    ];
+
+    it.each(testCases)(
+      'should handle $operand with value "$value"',
+      ({ operand, value, expected }) => {
+        const filter = createTestFilter(operand, value, 'BOOLEAN');
+        expect(buildValueFromFilter({ filter })).toBe(expected);
+      },
+    );
+  });
+
+  describe('ARRAY field type', () => {
+    const testCases = [
+      {
+        operand: ViewFilterOperand.Contains,
+        value: 'test',
+        expected: 'test',
+      },
+      {
+        operand: ViewFilterOperand.DoesNotContain,
+        value: 'test',
+        expected: undefined,
+      },
+      {
+        operand: ViewFilterOperand.IsNotEmpty,
+        value: 'test',
+        expected: 'test',
+      },
+      {
+        operand: ViewFilterOperand.IsEmpty,
+        value: 'test',
+        expected: undefined,
+      },
+    ];
+
+    it.each(testCases)(
+      'should handle $operand with value "$value"',
+      ({ operand, value, expected }) => {
+        const filter = createTestFilter(operand, value, 'ARRAY');
+        expect(buildValueFromFilter({ filter })).toBe(expected);
+      },
+    );
+  });
+
+  describe('RELATION field type', () => {
+    const mockCurrentWorkspaceMember = {
+      id: 'current-workspace-member-id',
+      name: { firstName: 'John', lastName: 'Doe' },
+      locale: 'en',
+      colorScheme: 'Light' as ColorScheme,
+      avatarUrl: '',
+      dateFormat: null,
+      timeFormat: null,
+      timeZone: null,
+    };
+
+    const testCases = [
+      {
+        operand: ViewFilterOperand.Is,
+        value: JSON.stringify({
+          isCurrentWorkspaceMemberSelected: false,
+          selectedRecordIds: ['record-1'],
+        }),
+        relationType: RelationDefinitionType.MANY_TO_ONE,
+        label: 'belongs to one',
+        expected: 'record-1',
+      },
+      {
+        operand: ViewFilterOperand.Is,
+        value: JSON.stringify({
+          isCurrentWorkspaceMemberSelected: true,
+          selectedRecordIds: ['record-1'],
+        }),
+        relationType: RelationDefinitionType.MANY_TO_ONE,
+        label: 'Assignee',
+        expected: 'current-workspace-member-id',
+      },
+      {
+        operand: ViewFilterOperand.Is,
+        value: JSON.stringify({
+          isCurrentWorkspaceMemberSelected: false,
+          selectedRecordIds: ['record-1', 'record-2'],
+        }),
+        relationType: RelationDefinitionType.MANY_TO_MANY,
+        label: 'hasmany',
+        expected: undefined,
+      },
+      {
+        operand: ViewFilterOperand.IsNot,
+        value: JSON.stringify({
+          isCurrentWorkspaceMemberSelected: false,
+          selectedRecordIds: ['record-1'],
+        }),
+        relationType: RelationDefinitionType.MANY_TO_ONE,
+        label: 'Assignee',
+        expected: undefined,
+      },
+      {
+        operand: ViewFilterOperand.IsEmpty,
+        value: JSON.stringify({
+          isCurrentWorkspaceMemberSelected: false,
+          selectedRecordIds: ['record-1'],
+        }),
+        relationType: RelationDefinitionType.MANY_TO_ONE,
+        label: 'Assignee',
+        expected: undefined,
+      },
+    ];
+
+    it.each(testCases)(
+      'should handle $operand with value "$value" for $relationType relation',
+      ({ operand, value, relationType, label, expected }) => {
+        const filter = createTestFilter(operand, value, 'RELATION');
+        expect(
+          buildValueFromFilter({
+            filter,
+            relationType,
+            currentWorkspaceMember: mockCurrentWorkspaceMember,
+            label,
+          }),
+        ).toEqual(expected);
+      },
+    );
+  });
+
+  describe('Composite field types', () => {
+    const compositeTypes: FilterableFieldType[] = ['ACTOR', 'FULL_NAME'];
+
+    it.each(compositeTypes)(
+      'should return undefined for composite type %s',
+      (type) => {
+        const filter = createTestFilter(ViewFilterOperand.Is, 'test', type);
+        expect(buildValueFromFilter({ filter })).toBeUndefined();
+      },
+    );
+  });
+
+  describe('RAW_JSON field type', () => {
+    it('should return undefined', () => {
+      const filter = createTestFilter(ViewFilterOperand.Is, 'test', 'RAW_JSON');
+      expect(buildValueFromFilter({ filter })).toBeUndefined();
+    });
+  });
+
+  describe('RATING field type', () => {
+    const mockOptions = [
+      {
+        label: 'Rating 1',
+        value: 'RATING_1',
+        id: '1',
+        position: 1,
+      },
+      {
+        label: 'Rating 2',
+        value: 'RATING_2',
+        id: '2',
+        position: 2,
+      },
+      {
+        label: 'Rating 3',
+        value: 'RATING_3',
+        id: '3',
+        position: 3,
+      },
+    ];
+
+    const testCases = [
+      {
+        operand: ViewFilterOperand.Is,
+        value: 'Rating 1',
+        expected: 'RATING_1',
+      },
+      {
+        operand: ViewFilterOperand.IsNotEmpty,
+        value: 'Rating 2',
+        expected: 'RATING_2',
+      },
+      {
+        operand: ViewFilterOperand.IsEmpty,
+        value: 'Rating 1',
+        expected: undefined,
+      },
+      {
+        operand: ViewFilterOperand.GreaterThan,
+        value: 'Rating 1',
+        expected: 'RATING_2',
+      },
+      {
+        operand: ViewFilterOperand.LessThan,
+        value: 'Rating 2',
+        expected: 'RATING_1',
+      },
+    ];
+
+    it.each(testCases)(
+      'should handle $operand with value "$value"',
+      ({ operand, value, expected }) => {
+        const filter = createTestFilter(operand, value, 'RATING');
+        expect(
+          buildValueFromFilter({
+            filter,
+            options: mockOptions as FieldMetadataItemOption[],
+          }),
+        ).toBe(expected);
+      },
+    );
+
+    it('should return undefined when option is not found', () => {
+      const filter = createTestFilter(
+        ViewFilterOperand.Is,
+        'Rating 4',
+        'RATING',
+      );
+      expect(
+        buildValueFromFilter({
+          filter,
+          options: mockOptions as FieldMetadataItemOption[],
+        }),
+      ).toBeUndefined();
+    });
+  });
+
+  describe('SELECT field type', () => {
+    const mockOptions = [
+      {
+        label: 'Option 1',
+        value: 'OPTION_1',
+        color: 'red',
+        id: '1',
+        position: 1,
+      },
+      {
+        label: 'Option 2',
+        value: 'OPTION_2',
+        color: 'blue',
+        id: '2',
+        position: 2,
+      },
+    ];
+
+    const testCases = [
+      {
+        operand: ViewFilterOperand.Is,
+        value: JSON.stringify(['OPTION_1']),
+        expected: 'OPTION_1',
+      },
+      {
+        operand: ViewFilterOperand.IsNot,
+        value: JSON.stringify(['OPTION_1']),
+        expected: undefined,
+      },
+      {
+        operand: ViewFilterOperand.IsEmpty,
+        value: JSON.stringify(['OPTION_1']),
+        expected: undefined,
+      },
+    ];
+
+    it.each(testCases)(
+      'should handle $operand with value "$value"',
+      ({ operand, value, expected }) => {
+        const filter = createTestFilter(operand, value, 'SELECT');
+        expect(
+          buildValueFromFilter({
+            filter,
+            options: mockOptions as FieldMetadataItemOption[],
+          }),
+        ).toBe(expected);
+      },
+    );
+
+    it('should handle invalid JSON', () => {
+      const filter = createTestFilter(
+        ViewFilterOperand.Is,
+        'invalid-json',
+        'SELECT',
+      );
+      expect(
+        buildValueFromFilter({
+          filter,
+          options: mockOptions as FieldMetadataItemOption[],
+        }),
+      ).toBeUndefined();
+    });
+  });
+
+  describe('MULTI_SELECT field type', () => {
+    const testCases = [
+      {
+        operand: ViewFilterOperand.Contains,
+        value: JSON.stringify(['OPTION_1', 'OPTION_2']),
+        expected: ['OPTION_1', 'OPTION_2'],
+      },
+      {
+        operand: ViewFilterOperand.DoesNotContain,
+        value: JSON.stringify(['OPTION_1']),
+        expected: undefined,
+      },
+      {
+        operand: ViewFilterOperand.IsEmpty,
+        value: JSON.stringify(['OPTION_1']),
+        expected: undefined,
+      },
+    ];
+
+    it.each(testCases)(
+      'should handle $operand with value "$value"',
+      ({ operand, value, expected }) => {
+        const filter = createTestFilter(operand, value, 'MULTI_SELECT');
+        expect(buildValueFromFilter({ filter })).toEqual(expected);
+      },
+    );
+
+    it('should handle invalid JSON', () => {
+      const filter = createTestFilter(
+        ViewFilterOperand.Contains,
+        'invalid-json',
+        'MULTI_SELECT',
+      );
+      expect(buildValueFromFilter({ filter })).toBeUndefined();
+    });
+  });
+});
diff --git a/packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.ts b/packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.ts
new file mode 100644
index 0000000000000..76becf2231a0b
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.ts
@@ -0,0 +1,299 @@
+import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState';
+import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
+import { isCompositeField } from '@/object-record/object-filter-dropdown/utils/isCompositeField';
+
+import {
+  RecordFilter,
+  RecordFilterToRecordInputOperand,
+} from '@/object-record/record-filter/types/RecordFilter';
+import { FILTER_OPERANDS_MAP } from '@/object-record/record-filter/utils/getRecordFilterOperands';
+import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
+import { assertUnreachable } from 'twenty-shared/utils';
+import { RelationDefinitionType } from '~/generated-metadata/graphql';
+import { parseJson } from '~/utils/parseJson';
+
+export const buildValueFromFilter = ({
+  filter,
+  options,
+  relationType,
+  currentWorkspaceMember,
+  label,
+}: {
+  filter: RecordFilter;
+  options?: FieldMetadataItemOption[];
+  relationType?: RelationDefinitionType;
+  currentWorkspaceMember?: CurrentWorkspaceMember;
+  label?: string;
+}) => {
+  if (isCompositeField(filter.type)) {
+    return;
+  }
+
+  if (filter.type === 'RAW_JSON') {
+    return;
+  }
+
+  const operands = FILTER_OPERANDS_MAP[filter.type];
+  if (!operands.some((operand) => operand === filter.operand)) {
+    throw new Error('Operand not supported for this field type');
+  }
+
+  switch (filter.type) {
+    case 'TEXT': {
+      return computeValueFromFilterText(
+        filter.operand as (typeof FILTER_OPERANDS_MAP)['TEXT'][number],
+        filter.value,
+      );
+    }
+    case 'RATING':
+      return computeValueFromFilterRating(
+        filter.operand as (typeof FILTER_OPERANDS_MAP)['RATING'][number],
+        filter.value,
+        options,
+      );
+    case 'DATE_TIME':
+    case 'DATE':
+      return computeValueFromFilterDate(
+        filter.operand as (typeof FILTER_OPERANDS_MAP)['DATE_TIME'][number],
+        filter.value,
+      );
+    case 'NUMBER':
+      return computeValueFromFilterNumber(
+        filter.operand as (typeof FILTER_OPERANDS_MAP)['NUMBER'][number],
+        filter.value,
+      );
+    case 'BOOLEAN':
+      return computeValueFromFilterBoolean(
+        filter.operand as (typeof FILTER_OPERANDS_MAP)['BOOLEAN'][number],
+        filter.value,
+      );
+    case 'ARRAY':
+      return computeValueFromFilterArray(
+        filter.operand as (typeof FILTER_OPERANDS_MAP)['ARRAY'][number],
+        filter.value,
+      );
+    case 'SELECT':
+      return computeValueFromFilterSelect(
+        filter.operand as (typeof FILTER_OPERANDS_MAP)['SELECT'][number],
+        filter.value,
+        options,
+      );
+    case 'MULTI_SELECT':
+      return computeValueFromFilterMultiSelect(
+        filter.operand as (typeof FILTER_OPERANDS_MAP)['MULTI_SELECT'][number],
+        filter.value,
+      );
+    case 'RELATION': {
+      return computeValueFromFilterRelation(
+        filter.operand as (typeof FILTER_OPERANDS_MAP)['RELATION'][number],
+        filter.value,
+        relationType,
+        currentWorkspaceMember,
+        label,
+      );
+    }
+    default:
+      assertUnreachable(filter.type);
+  }
+};
+
+const computeValueFromFilterText = (
+  operand: RecordFilterToRecordInputOperand<'TEXT'>,
+  value: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.Contains:
+      return value;
+    case ViewFilterOperand.IsNotEmpty:
+      return value;
+    case ViewFilterOperand.IsEmpty:
+    case ViewFilterOperand.DoesNotContain:
+      return undefined;
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterDate = (
+  operand: RecordFilterToRecordInputOperand<'DATE_TIME'>,
+  value: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.Is:
+    case ViewFilterOperand.IsAfter:
+    case ViewFilterOperand.IsBefore:
+      return new Date(value);
+    case ViewFilterOperand.IsToday:
+    case ViewFilterOperand.IsNotEmpty:
+    case ViewFilterOperand.IsInPast:
+    case ViewFilterOperand.IsInFuture:
+    case ViewFilterOperand.IsRelative:
+      return new Date();
+    case ViewFilterOperand.IsEmpty:
+      return undefined;
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterNumber = (
+  operand: RecordFilterToRecordInputOperand<'NUMBER'>,
+  value: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.GreaterThan:
+      return Number(value) + 1;
+    case ViewFilterOperand.LessThan:
+      return Number(value) - 1;
+    case ViewFilterOperand.IsNotEmpty:
+      return Number(value);
+    case ViewFilterOperand.IsEmpty:
+      return undefined;
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterBoolean = (
+  operand: RecordFilterToRecordInputOperand<'BOOLEAN'>,
+  value: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.Is:
+      return value === 'true';
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterArray = (
+  operand: RecordFilterToRecordInputOperand<'ARRAY'>,
+  value: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.Contains:
+    case ViewFilterOperand.IsNotEmpty:
+      return value;
+    case ViewFilterOperand.DoesNotContain:
+    case ViewFilterOperand.IsEmpty:
+      return undefined;
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterRating = (
+  operand: RecordFilterToRecordInputOperand<'RATING'>,
+  value: string,
+  options?: FieldMetadataItemOption[],
+) => {
+  const option = options?.find((option) => option.label === value);
+  if (!option) {
+    return undefined;
+  }
+
+  switch (operand) {
+    case ViewFilterOperand.Is:
+    case ViewFilterOperand.IsNotEmpty:
+      return option.value;
+    case ViewFilterOperand.GreaterThan: {
+      const plusOne = options?.find(
+        (opt) => opt.position === option.position + 1,
+      )?.value;
+      return plusOne ? plusOne : option.value;
+    }
+    case ViewFilterOperand.LessThan: {
+      const minusOne = options?.find(
+        (opt) => opt.position === option.position - 1,
+      )?.value;
+      return minusOne ? minusOne : option.value;
+    }
+    case ViewFilterOperand.IsEmpty:
+      return undefined;
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterSelect = (
+  operand: RecordFilterToRecordInputOperand<'SELECT'>,
+  value: string,
+  options?: FieldMetadataItemOption[],
+) => {
+  switch (operand) {
+    case ViewFilterOperand.Is:
+    case ViewFilterOperand.IsNotEmpty:
+      try {
+        const valueParsed = parseJson<string[]>(value)?.[0];
+        const option = options?.find((option) => option.value === valueParsed);
+        if (!option) {
+          return undefined;
+        }
+        return option.value;
+      } catch (error) {
+        return undefined;
+      }
+    case ViewFilterOperand.IsNot:
+    case ViewFilterOperand.IsEmpty:
+      return undefined;
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterMultiSelect = (
+  operand: RecordFilterToRecordInputOperand<'MULTI_SELECT'>,
+  value: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.Contains:
+    case ViewFilterOperand.IsNotEmpty:
+      try {
+        const parsedValue = parseJson<string[]>(value);
+        return parsedValue ? parsedValue : undefined;
+      } catch (error) {
+        return undefined;
+      }
+    case ViewFilterOperand.DoesNotContain:
+    case ViewFilterOperand.IsEmpty:
+      return undefined;
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterRelation = (
+  operand: RecordFilterToRecordInputOperand<'RELATION'>,
+  value: string,
+  relationType?: RelationDefinitionType,
+  currentWorkspaceMember?: CurrentWorkspaceMember,
+  label?: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.Is: {
+      const parsedValue = parseJson<{
+        isCurrentWorkspaceMemberSelected: boolean;
+        selectedRecordIds: string[];
+      }>(value);
+      if (
+        relationType === RelationDefinitionType.MANY_TO_ONE ||
+        relationType === RelationDefinitionType.ONE_TO_ONE
+      ) {
+        if (label === 'Assignee') {
+          return parsedValue?.isCurrentWorkspaceMemberSelected
+            ? currentWorkspaceMember?.id
+            : undefined;
+        } else {
+          return parsedValue?.selectedRecordIds?.[0];
+        }
+      }
+      return undefined; //todo
+    }
+    case ViewFilterOperand.IsNot:
+    case ViewFilterOperand.IsNotEmpty: // todo
+    case ViewFilterOperand.IsEmpty:
+      return undefined;
+    default:
+      assertUnreachable(operand);
+  }
+};


Reviews:
user: login: greptile-apps[bot]
body: 
## PR Summary

This PR adds functionality to automatically apply filter values when creating new records in filtered views, ensuring records remain visible in filtered views by inheriting filter criteria.

- Added `useBuildRecordInputFromFilters` hook in `/packages/twenty-front/src/modules/object-record/record-table/hooks/useBuildRecordInputFromFilters.ts` to convert view filters into record input values
- Added `buildValueFromFilter` utility in `/packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.ts` with comprehensive test coverage for different field types
- Added `RecordFilterInput` and `RecordFilterToRecordInputOperand` types in `/packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilter.ts` for type-safe filter conversion
- Fixed duplicate CSS properties in `StyledBar` component in `/packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx`
- Potential issue: Unconditional `openInlineCell()` call in `RecordTitleCellFieldInput` could cause unexpected behavior

<sub>9 file(s) reviewed, 12 comment(s)</sub>
<sub>[Edit PR Review Bot Settings](https://app.greptile.com/review/github) | [Greptile](https://greptile.com?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews&utm_content=twenty_11717)</sub>
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T11:56:06Z
user: login: Devessier
body: Great job! I like seeing stricter types like that. I lack context and won't approve the PR for this reason, but I want to left two small comments.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T13:01:18Z
user: login: prastoin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T13:53:09Z
user: login: prastoin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T13:54:54Z
user: login: prastoin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T13:56:27Z
user: login: prastoin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T13:58:01Z
user: login: etiennejouan
body: Left few comments. "Bravo" nice feature 👏 
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T13:58:26Z
user: login: prastoin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T13:58:41Z
user: login: prastoin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T14:04:19Z
user: login: prastoin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T14:07:23Z
user: login: prastoin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T14:09:56Z
user: login: prastoin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T14:12:18Z
user: login: prastoin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T14:13:26Z
user: login: prastoin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T14:15:38Z
user: login: prastoin
body: ## Introduction
Strong and good job over there, very nice type inferring from constants + assertUnreachable. Feel stricter than the usual ! Well done !

Left several comments ! Available to discuss about them nor give an handon in their implementation if needed

## Notes:
- To avoid having to force cast `        filter.operand as RecordFilterInput<'RATING'>['operand'],
` computeValue calls we could centralize them directly within the `FILTER_OPERANDS_MAP`
- Overall I feel like the `RecordFilter` should be stricter and should be using a generic as we've tried to implem in order to restrict operands for a given `RecordFilter Type`, @lucasbordeau do you have any opinion on this ? Started an implementation [here](https://github.com/twentyhq/twenty/commit/5e01e22b0c7e60d469a590b94460a5c7d6dff56c)
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T14:18:28Z
user: login: guillim
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T14:30:07Z
user: login: guillim
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T14:54:15Z
user: login: guillim
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T15:13:26Z
user: login: etiennejouan
body: LGTM 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T16:01:06Z

Review comments:
diff_hunk: @@ -27,16 +28,28 @@ export const useCreateNewIndexRecord = ({
 
   const { openRecordTitleCell } = useRecordTitleCell();
 
+  //tododo here
path: packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewIndexRecord.ts
user: login: greptile-apps[bot]
body: style: Remove development comment 'tododo here'

```suggestion

```
created_at: 2025-04-24T11:54:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -27,16 +28,28 @@ export const useCreateNewIndexRecord = ({
 
   const { openRecordTitleCell } = useRecordTitleCell();
 
+  //tododo here
+  const { buildRecordInputFromFilters } = useBuildRecordInputFromFilters({
+    objectMetadataItem,
+  });
+
   const createNewIndexRecord = useRecoilCallback(
     ({ snapshot }) =>
       async (recordInput?: Partial<ObjectRecord>) => {
         const recordId = v4();
+        const recordInputFromFilters = buildRecordInputFromFilters();
 
         const recordIndexOpenRecordIn = snapshot
           .getLoadable(recordIndexOpenRecordInState)
           .getValue();
 
-        await createOneRecord({ id: recordId, ...recordInput });
+        await createOneRecord({
+          id: recordId,
+          ...{
+            ...recordInputFromFilters,
+            ...recordInput,
+          },
+        });
path: packages/twenty-front/src/modules/object-record/record-table/hooks/useCreateNewIndexRecord.ts
user: login: greptile-apps[bot]
body: style: Spreading objects twice is unnecessary. Can be simplified to: `await createOneRecord({ id: recordId, ...recordInputFromFilters, ...recordInput });`

```suggestion
        await createOneRecord({
          id: recordId,
          ...recordInputFromFilters,
          ...recordInput,
        });
```
created_at: 2025-04-24T11:54:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,55 @@
+import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
+import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
+import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
+import { buildValueFromFilter } from '@/object-record/record-table/utils/buildRecordInputFromFilter';
+
+import { ObjectRecord } from '@/object-record/types/ObjectRecord';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+import { useRecoilValue } from 'recoil';
+import { isDefined } from 'twenty-shared/utils';
+
+export const useBuildRecordInputFromFilters = ({
+  objectMetadataItem,
+}: {
+  objectMetadataItem: ObjectMetadataItem;
+}) => {
+  // we might need to build a recoil callback for better performance
+  const currentRecordFilters = useRecoilComponentValueV2(
+    currentRecordFiltersComponentState,
+  );
+
+  const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
+
+  const buildRecordInputFromFilters = (): Partial<ObjectRecord> => {
+    const recordInput: Partial<ObjectRecord> = {};
+
+    currentRecordFilters.forEach((filter) => {
+      const fieldMetadataItem = objectMetadataItem.fields.find(
+        (field) => field.id === filter.fieldMetadataId,
+      );
+
+      if (isDefined(fieldMetadataItem)) {
+        if (fieldMetadataItem.type === 'RELATION') {
+          if (isDefined(filter.value)) {
+            recordInput[`${fieldMetadataItem.name}Id`] = buildValueFromFilter({
+              filter,
+              options: fieldMetadataItem.options ?? undefined,
+              relationType: fieldMetadataItem.relationDefinition?.direction,
+              currentWorkspaceMember: currentWorkspaceMember ?? undefined,
+              label: filter.label,
+            });
+          }
+        } else {
+          recordInput[fieldMetadataItem.name] = buildValueFromFilter({
+            filter,
+            options: fieldMetadataItem.options ?? undefined,
+          });
+        }
path: packages/twenty-front/src/modules/object-record/record-table/hooks/useBuildRecordInputFromFilters.ts
user: login: greptile-apps[bot]
body: logic: Missing check for filter.value in non-relation case, could lead to undefined being set unnecessarily
created_at: 2025-04-24T11:54:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,431 @@
+import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
+import { FilterableFieldType } from '@/object-record/record-filter/types/FilterableFieldType';
+import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
+import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
+import { buildValueFromFilter } from './buildRecordInputFromFilter';
+
+// TODO: fix the dates, and test the not supported types
+const mockDate = new Date('2024-03-20T12:00:00Z');
+
+beforeAll(() => {
+  jest.useFakeTimers();
+  jest.setSystemTime(mockDate);
+});
+
+afterAll(() => {
+  jest.useRealTimers();
+});
+
+describe('buildValueFromFilter', () => {
+  const createTestFilter = (
+    operand: ViewFilterOperand,
+    value: string,
+    type: FilterableFieldType,
+  ): RecordFilter => ({
+    id: 'test-id',
+    fieldMetadataId: 'test-field-id',
+    value,
+    displayValue: value,
+    type,
+    operand,
+    label: 'Test Label',
+  });
+
+  describe('TEXT field type', () => {
+    const testCases = [
+      {
+        operand: ViewFilterOperand.Contains,
+        value: 'test',
+        expected: 'test',
+      },
+      {
+        operand: ViewFilterOperand.DoesNotContain,
+        value: 'test',
+        expected: undefined,
+      },
+      {
+        operand: ViewFilterOperand.IsNotEmpty,
+        value: 'test',
+        expected: 'test',
+      },
+      {
+        operand: ViewFilterOperand.IsEmpty,
+        value: 'test',
+        expected: undefined,
+      },
+    ];
+
+    it.each(testCases)(
+      'should handle $operand with value "$value"',
+      ({ operand, value, expected }) => {
+        const filter = createTestFilter(operand, value, 'TEXT');
+        expect(buildValueFromFilter({ filter })).toBe(expected);
+      },
+    );
+  });
+
+  describe('DATE_TIME field type', () => {
+    const testCases = [
+      {
+        operand: ViewFilterOperand.Is,
+        value: '2024-03-20T12:00:00Z',
+        expected: mockDate,
+      },
+      {
+        operand: ViewFilterOperand.IsAfter,
+        value: '2024-03-20T12:00:00Z',
+        expected: mockDate,
+      },
+      {
+        operand: ViewFilterOperand.IsBefore,
+        value: '2024-03-20T12:00:00Z',
+        expected: mockDate,
+      },
+      {
+        operand: ViewFilterOperand.IsInPast,
+        value: '2024-03-20T12:00:00Z',
+        expected: mockDate,
+      },
+      {
+        operand: ViewFilterOperand.IsInFuture,
+        value: '2024-03-20T12:00:00Z',
+        expected: mockDate,
+      },
+      {
+        operand: ViewFilterOperand.IsToday,
+        value: '',
+        expected: mockDate,
+      },
+      {
+        operand: ViewFilterOperand.IsRelative,
+        value: '',
+        expected: mockDate,
+      },
+      {
+        operand: ViewFilterOperand.IsEmpty,
+        value: '',
+        expected: undefined,
+      },
+      {
+        operand: ViewFilterOperand.IsNotEmpty,
+        value: '2024-03-20T12:00:00Z',
+        expected: mockDate,
+      },
+    ];
+
+    it.each(testCases)(
+      'should handle $operand with value "$value"',
+      ({ operand, value, expected }) => {
+        const filter = createTestFilter(operand, value, 'DATE_TIME');
+        const result = buildValueFromFilter({ filter });
+        if (expected instanceof Date) {
+          expect(result).toBeInstanceOf(Date);
+          expect(result).toEqual(expected);
+        } else {
+          expect(result).toBe(expected);
+        }
+      },
+    );
+  });
+
+  describe('NUMBER field type', () => {
+    const testCases = [
+      {
+        operand: ViewFilterOperand.GreaterThan,
+        value: '5',
+        expected: 6,
+      },
path: packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.spec.ts
user: login: greptile-apps[bot]
body: logic: GreaterThan test case assumes value + 1, which may not match the actual implementation's behavior for number comparisons
created_at: 2025-04-24T11:54:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,431 @@
+import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
+import { FilterableFieldType } from '@/object-record/record-filter/types/FilterableFieldType';
+import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
+import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
+import { buildValueFromFilter } from './buildRecordInputFromFilter';
+
+// TODO: fix the dates, and test the not supported types
+const mockDate = new Date('2024-03-20T12:00:00Z');
+
+beforeAll(() => {
+  jest.useFakeTimers();
+  jest.setSystemTime(mockDate);
+});
+
+afterAll(() => {
+  jest.useRealTimers();
+});
+
+describe('buildValueFromFilter', () => {
+  const createTestFilter = (
+    operand: ViewFilterOperand,
+    value: string,
+    type: FilterableFieldType,
+  ): RecordFilter => ({
+    id: 'test-id',
+    fieldMetadataId: 'test-field-id',
+    value,
+    displayValue: value,
+    type,
+    operand,
+    label: 'Test Label',
+  });
+
+  describe('TEXT field type', () => {
+    const testCases = [
+      {
+        operand: ViewFilterOperand.Contains,
+        value: 'test',
+        expected: 'test',
+      },
+      {
+        operand: ViewFilterOperand.DoesNotContain,
+        value: 'test',
+        expected: undefined,
+      },
+      {
+        operand: ViewFilterOperand.IsNotEmpty,
+        value: 'test',
+        expected: 'test',
+      },
+      {
+        operand: ViewFilterOperand.IsEmpty,
+        value: 'test',
+        expected: undefined,
+      },
+    ];
+
+    it.each(testCases)(
+      'should handle $operand with value "$value"',
+      ({ operand, value, expected }) => {
+        const filter = createTestFilter(operand, value, 'TEXT');
+        expect(buildValueFromFilter({ filter })).toBe(expected);
+      },
+    );
+  });
+
+  describe('DATE_TIME field type', () => {
+    const testCases = [
+      {
+        operand: ViewFilterOperand.Is,
+        value: '2024-03-20T12:00:00Z',
+        expected: mockDate,
+      },
+      {
+        operand: ViewFilterOperand.IsAfter,
+        value: '2024-03-20T12:00:00Z',
+        expected: mockDate,
+      },
+      {
+        operand: ViewFilterOperand.IsBefore,
+        value: '2024-03-20T12:00:00Z',
+        expected: mockDate,
+      },
+      {
+        operand: ViewFilterOperand.IsInPast,
+        value: '2024-03-20T12:00:00Z',
+        expected: mockDate,
+      },
+      {
+        operand: ViewFilterOperand.IsInFuture,
+        value: '2024-03-20T12:00:00Z',
+        expected: mockDate,
+      },
+      {
+        operand: ViewFilterOperand.IsToday,
+        value: '',
+        expected: mockDate,
+      },
+      {
+        operand: ViewFilterOperand.IsRelative,
+        value: '',
+        expected: mockDate,
+      },
+      {
+        operand: ViewFilterOperand.IsEmpty,
+        value: '',
+        expected: undefined,
+      },
+      {
+        operand: ViewFilterOperand.IsNotEmpty,
+        value: '2024-03-20T12:00:00Z',
+        expected: mockDate,
+      },
+    ];
+
+    it.each(testCases)(
+      'should handle $operand with value "$value"',
+      ({ operand, value, expected }) => {
+        const filter = createTestFilter(operand, value, 'DATE_TIME');
+        const result = buildValueFromFilter({ filter });
+        if (expected instanceof Date) {
+          expect(result).toBeInstanceOf(Date);
+          expect(result).toEqual(expected);
+        } else {
+          expect(result).toBe(expected);
+        }
+      },
+    );
+  });
+
+  describe('NUMBER field type', () => {
+    const testCases = [
+      {
+        operand: ViewFilterOperand.GreaterThan,
+        value: '5',
+        expected: 6,
+      },
+      {
+        operand: ViewFilterOperand.LessThan,
+        value: '5',
+        expected: 4,
+      },
+      {
+        operand: ViewFilterOperand.IsNotEmpty,
+        value: '5',
+        expected: 5,
+      },
+      {
+        operand: ViewFilterOperand.IsEmpty,
+        value: '5',
+        expected: undefined,
+      },
+    ];
+
+    it.each(testCases)(
+      'should handle $operand with value "$value"',
+      ({ operand, value, expected }) => {
+        const filter = createTestFilter(operand, value, 'NUMBER');
+        expect(buildValueFromFilter({ filter })).toBe(expected);
+      },
+    );
+  });
+
+  describe('BOOLEAN field type', () => {
+    const testCases = [
+      {
+        operand: ViewFilterOperand.Is,
+        value: 'true',
+        expected: true,
+      },
+      {
+        operand: ViewFilterOperand.Is,
+        value: 'false',
+        expected: false,
+      },
+    ];
+
+    it.each(testCases)(
+      'should handle $operand with value "$value"',
+      ({ operand, value, expected }) => {
+        const filter = createTestFilter(operand, value, 'BOOLEAN');
+        expect(buildValueFromFilter({ filter })).toBe(expected);
+      },
+    );
+  });
+
+  describe('ARRAY field type', () => {
+    const testCases = [
+      {
+        operand: ViewFilterOperand.Contains,
+        value: 'test',
+        expected: 'test',
+      },
+      {
+        operand: ViewFilterOperand.DoesNotContain,
+        value: 'test',
+        expected: undefined,
+      },
+      {
+        operand: ViewFilterOperand.IsNotEmpty,
+        value: 'test',
+        expected: 'test',
+      },
+      {
+        operand: ViewFilterOperand.IsEmpty,
+        value: 'test',
+        expected: undefined,
+      },
+    ];
+
+    it.each(testCases)(
+      'should handle $operand with value "$value"',
+      ({ operand, value, expected }) => {
+        const filter = createTestFilter(operand, value, 'ARRAY');
+        expect(buildValueFromFilter({ filter })).toBe(expected);
+      },
+    );
+  });
+
+  describe('Unsupported field types', () => {
+    const unsupportedTypes: FilterableFieldType[] = ['RELATION'];
+
+    it.each(unsupportedTypes)(
+      'should throw error for unsupported type %s',
+      (type) => {
+        const filter = createTestFilter(ViewFilterOperand.Is, 'test', type);
+        expect(() => buildValueFromFilter({ filter })).toThrow(
+          'Type not supported',
+        );
+      },
+    );
+  });
+
+  describe('Composite field types', () => {
+    const compositeTypes: FilterableFieldType[] = ['ACTOR', 'FULL_NAME'];
+
+    it.each(compositeTypes)(
+      'should return undefined for composite type %s',
+      (type) => {
+        const filter = createTestFilter(ViewFilterOperand.Is, 'test', type);
+        expect(buildValueFromFilter({ filter })).toBeUndefined();
+      },
+    );
+  });
+
+  describe('RAW_JSON field type', () => {
+    it('should return undefined', () => {
+      const filter = createTestFilter(ViewFilterOperand.Is, 'test', 'RAW_JSON');
+      expect(buildValueFromFilter({ filter })).toBeUndefined();
+    });
+  });
+
+  describe('RATING field type', () => {
+    const mockOptions = [
+      {
+        label: 'Rating 1',
+        value: 'RATING_1',
+        id: '1',
+        position: 1,
+      },
+      {
+        label: 'Rating 2',
+        value: 'RATING_2',
+        id: '2',
+        position: 2,
+      },
+      {
+        label: 'Rating 3',
+        value: 'RATING_3',
+        id: '3',
+        position: 3,
+      },
+    ];
+
+    const testCases = [
+      {
+        operand: ViewFilterOperand.Is,
+        value: 'Rating 1',
+        expected: 'RATING_1',
+      },
+      {
+        operand: ViewFilterOperand.IsNotEmpty,
+        value: 'Rating 2',
+        expected: 'RATING_2',
+      },
+      {
+        operand: ViewFilterOperand.IsEmpty,
+        value: 'Rating 1',
+        expected: undefined,
+      },
+      {
+        operand: ViewFilterOperand.GreaterThan,
+        value: 'Rating 1',
+        expected: 'RATING_1',
+      },
+      {
+        operand: ViewFilterOperand.LessThan,
+        value: 'Rating 2',
+        expected: 'RATING_2',
+      },
path: packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.spec.ts
user: login: greptile-apps[bot]
body: logic: GreaterThan and LessThan for RATING type return raw values instead of computing relative ratings based on position

```suggestion
      {
        operand: ViewFilterOperand.GreaterThan,
        value: 'Rating 1',
        expected: 'RATING_2', // Should expect next rating based on position
      },
      {
        operand: ViewFilterOperand.LessThan,
        value: 'Rating 2',
        expected: 'RATING_1', // Should expect previous rating based on position
      },
```
created_at: 2025-04-24T11:54:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -33,7 +34,8 @@ export const RecordTitleCellFieldInput = ({
   if (!isFieldText(fieldDefinition) && !isFieldFullName(fieldDefinition)) {
     throw new Error('Field definition is not a text or full name field');
   }
-
+  const { openInlineCell } = useInlineCell();
+  openInlineCell();
path: packages/twenty-front/src/modules/object-record/record-title-cell/components/RecordTitleCellFieldInput.tsx
user: login: greptile-apps[bot]
body: logic: Unconditional call to openInlineCell() could cause issues if the component re-renders. Consider moving this to an effect or only calling when needed.
created_at: 2025-04-24T11:55:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,283 @@
+import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState';
+import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
+import { isCompositeField } from '@/object-record/object-filter-dropdown/utils/isCompositeField';
+
+import {
+  RecordFilter,
+  RecordFilterInput,
+  RecordFilterToRecordInputOperand,
+} from '@/object-record/record-filter/types/RecordFilter';
+import { FILTER_OPERANDS_MAP } from '@/object-record/record-filter/utils/getRecordFilterOperands';
+import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
+import { assertUnreachable } from 'twenty-shared/utils';
+import { RelationDefinitionType } from '~/generated-metadata/graphql';
+
+export const buildValueFromFilter = ({
+  filter,
+  options,
+  relationType,
+  currentWorkspaceMember,
+  label,
+}: {
+  filter: RecordFilter;
+  options?: FieldMetadataItemOption[];
+  relationType?: RelationDefinitionType;
+  currentWorkspaceMember?: CurrentWorkspaceMember;
+  label?: string;
+}) => {
+  if (isCompositeField(filter.type)) {
+    return;
+  }
+
+  if (filter.type === 'RAW_JSON') {
+    return;
+  }
+
+  const operands = FILTER_OPERANDS_MAP[filter.type];
+  if (!operands.some((operand) => operand === filter.operand)) {
+    throw new Error('Operand not supported for this field type');
+  }
+
+  switch (filter.type) {
+    case 'TEXT': {
+      return computeValueFromFilterText(
+        filter.operand as RecordFilterInput<'TEXT'>['operand'],
+        filter.value,
+      );
+    }
+    case 'RATING':
+      return computeValueFromFilterRating(
+        filter.operand as RecordFilterInput<'RATING'>['operand'],
+        filter.value,
+        options,
+      );
+    case 'DATE_TIME':
+    case 'DATE':
+      return computeValueFromFilterDate(
+        filter.operand as RecordFilterInput<'DATE_TIME'>['operand'],
+        filter.value,
+      );
+    case 'NUMBER':
+      return computeValueFromFilterNumber(
+        filter.operand as RecordFilterInput<'NUMBER'>['operand'],
+        filter.value,
+      );
+    case 'BOOLEAN':
+      return computeValueFromFilterBoolean(
+        filter.operand as RecordFilterInput<'BOOLEAN'>['operand'],
+        filter.value,
+      );
+    case 'ARRAY':
+      return computeValueFromFilterArray(
+        filter.operand as RecordFilterInput<'ARRAY'>['operand'],
+        filter.value,
+      );
+    case 'SELECT':
+      return computeValueFromFilterSelect(
+        filter.operand as RecordFilterInput<'SELECT'>['operand'],
+        filter.value,
+        options,
+      );
+    case 'MULTI_SELECT':
+      return computeValueFromFilterMultiSelect(
+        filter.operand as RecordFilterInput<'MULTI_SELECT'>['operand'],
+        filter.value,
+      );
+    case 'RELATION': {
+      return computeValueFromFilterRelation(
+        filter.operand as RecordFilterInput<'RELATION'>['operand'],
+        filter.value,
+        relationType,
+        currentWorkspaceMember,
+        label,
+      );
+    }
+    default:
+      assertUnreachable(filter.type);
+  }
+};
+
+const computeValueFromFilterText = (
+  operand: RecordFilterToRecordInputOperand<'TEXT'>,
+  value: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.Contains:
+      return value;
+    case ViewFilterOperand.IsNotEmpty:
+      return value;
+    case ViewFilterOperand.IsEmpty:
+    case ViewFilterOperand.DoesNotContain:
+      return undefined;
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterDate = (
+  operand: RecordFilterToRecordInputOperand<'DATE_TIME'>,
+  value: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.Is:
+    case ViewFilterOperand.IsNotEmpty:
+    case ViewFilterOperand.IsAfter:
+    case ViewFilterOperand.IsBefore:
+    case ViewFilterOperand.IsInPast:
+    case ViewFilterOperand.IsInFuture:
+      return new Date(value);
+    case ViewFilterOperand.IsToday:
+    case ViewFilterOperand.IsRelative:
+      return new Date();
+    case ViewFilterOperand.IsEmpty:
+      return undefined;
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterNumber = (
+  operand: RecordFilterToRecordInputOperand<'NUMBER'>,
+  value: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.GreaterThan:
+      return Number(value) + 1;
+    case ViewFilterOperand.LessThan:
+      return Number(value) - 1;
+    case ViewFilterOperand.IsNotEmpty:
path: packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.ts
user: login: greptile-apps[bot]
body: logic: Adding/subtracting 1 from numbers for GreaterThan/LessThan could cause precision issues with decimal values
created_at: 2025-04-24T11:55:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,283 @@
+import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState';
+import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
+import { isCompositeField } from '@/object-record/object-filter-dropdown/utils/isCompositeField';
+
+import {
+  RecordFilter,
+  RecordFilterInput,
+  RecordFilterToRecordInputOperand,
+} from '@/object-record/record-filter/types/RecordFilter';
+import { FILTER_OPERANDS_MAP } from '@/object-record/record-filter/utils/getRecordFilterOperands';
+import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
+import { assertUnreachable } from 'twenty-shared/utils';
+import { RelationDefinitionType } from '~/generated-metadata/graphql';
+
+export const buildValueFromFilter = ({
+  filter,
+  options,
+  relationType,
+  currentWorkspaceMember,
+  label,
+}: {
+  filter: RecordFilter;
+  options?: FieldMetadataItemOption[];
+  relationType?: RelationDefinitionType;
+  currentWorkspaceMember?: CurrentWorkspaceMember;
+  label?: string;
+}) => {
+  if (isCompositeField(filter.type)) {
+    return;
+  }
+
+  if (filter.type === 'RAW_JSON') {
+    return;
+  }
+
+  const operands = FILTER_OPERANDS_MAP[filter.type];
+  if (!operands.some((operand) => operand === filter.operand)) {
+    throw new Error('Operand not supported for this field type');
+  }
+
+  switch (filter.type) {
+    case 'TEXT': {
+      return computeValueFromFilterText(
+        filter.operand as RecordFilterInput<'TEXT'>['operand'],
+        filter.value,
+      );
+    }
+    case 'RATING':
+      return computeValueFromFilterRating(
+        filter.operand as RecordFilterInput<'RATING'>['operand'],
+        filter.value,
+        options,
+      );
+    case 'DATE_TIME':
+    case 'DATE':
+      return computeValueFromFilterDate(
+        filter.operand as RecordFilterInput<'DATE_TIME'>['operand'],
+        filter.value,
+      );
+    case 'NUMBER':
+      return computeValueFromFilterNumber(
+        filter.operand as RecordFilterInput<'NUMBER'>['operand'],
+        filter.value,
+      );
+    case 'BOOLEAN':
+      return computeValueFromFilterBoolean(
+        filter.operand as RecordFilterInput<'BOOLEAN'>['operand'],
+        filter.value,
+      );
+    case 'ARRAY':
+      return computeValueFromFilterArray(
+        filter.operand as RecordFilterInput<'ARRAY'>['operand'],
+        filter.value,
+      );
+    case 'SELECT':
+      return computeValueFromFilterSelect(
+        filter.operand as RecordFilterInput<'SELECT'>['operand'],
+        filter.value,
+        options,
+      );
+    case 'MULTI_SELECT':
+      return computeValueFromFilterMultiSelect(
+        filter.operand as RecordFilterInput<'MULTI_SELECT'>['operand'],
+        filter.value,
+      );
+    case 'RELATION': {
+      return computeValueFromFilterRelation(
+        filter.operand as RecordFilterInput<'RELATION'>['operand'],
+        filter.value,
+        relationType,
+        currentWorkspaceMember,
+        label,
+      );
+    }
+    default:
+      assertUnreachable(filter.type);
+  }
+};
+
+const computeValueFromFilterText = (
+  operand: RecordFilterToRecordInputOperand<'TEXT'>,
+  value: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.Contains:
+      return value;
+    case ViewFilterOperand.IsNotEmpty:
+      return value;
+    case ViewFilterOperand.IsEmpty:
+    case ViewFilterOperand.DoesNotContain:
+      return undefined;
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterDate = (
+  operand: RecordFilterToRecordInputOperand<'DATE_TIME'>,
+  value: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.Is:
+    case ViewFilterOperand.IsNotEmpty:
+    case ViewFilterOperand.IsAfter:
+    case ViewFilterOperand.IsBefore:
+    case ViewFilterOperand.IsInPast:
+    case ViewFilterOperand.IsInFuture:
+      return new Date(value);
+    case ViewFilterOperand.IsToday:
+    case ViewFilterOperand.IsRelative:
+      return new Date();
+    case ViewFilterOperand.IsEmpty:
+      return undefined;
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterNumber = (
+  operand: RecordFilterToRecordInputOperand<'NUMBER'>,
+  value: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.GreaterThan:
+      return Number(value) + 1;
+    case ViewFilterOperand.LessThan:
+      return Number(value) - 1;
+    case ViewFilterOperand.IsNotEmpty:
+      return Number(value);
+    case ViewFilterOperand.IsEmpty:
+      return undefined;
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterBoolean = (
+  operand: RecordFilterToRecordInputOperand<'BOOLEAN'>,
+  value: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.Is:
+      return value === 'true';
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterArray = (
+  operand: RecordFilterToRecordInputOperand<'ARRAY'>,
+  value: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.Contains:
+    case ViewFilterOperand.IsNotEmpty:
+      return value;
+    case ViewFilterOperand.DoesNotContain:
+    case ViewFilterOperand.IsEmpty:
+      return undefined;
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterRating = (
+  operand: RecordFilterToRecordInputOperand<'RATING'>,
+  value: string,
+  options?: FieldMetadataItemOption[],
+) => {
+  const option = options?.find((option) => option.label === value);
+  if (!option) {
+    return undefined;
+  }
+
+  switch (operand) {
+    case ViewFilterOperand.Is:
+    case ViewFilterOperand.IsNotEmpty:
+    case ViewFilterOperand.GreaterThan:
+    case ViewFilterOperand.LessThan:
+      return option.value;
+    case ViewFilterOperand.IsEmpty:
+      return undefined;
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterSelect = (
+  operand: RecordFilterToRecordInputOperand<'SELECT'>,
+  value: string,
+  options?: FieldMetadataItemOption[],
+) => {
+  switch (operand) {
+    case ViewFilterOperand.Is:
+    case ViewFilterOperand.IsNotEmpty:
+      try {
+        const valueParsed = JSON.parse(value)?.[0];
+        const option = options?.find((option) => option.value === valueParsed);
+        if (!option) {
+          return undefined;
+        }
+        return option.value;
+      } catch (error) {
+        return undefined;
+      }
+    case ViewFilterOperand.IsNot:
+    case ViewFilterOperand.IsEmpty:
+      return undefined;
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterMultiSelect = (
+  operand: RecordFilterToRecordInputOperand<'MULTI_SELECT'>,
+  value: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.Contains:
+    case ViewFilterOperand.IsNotEmpty:
+      try {
+        return JSON.parse(value);
+      } catch (error) {
+        return undefined;
+      }
+    case ViewFilterOperand.DoesNotContain:
+    case ViewFilterOperand.IsEmpty:
+      return undefined;
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterRelation = (
+  operand: RecordFilterToRecordInputOperand<'RELATION'>,
+  value: string,
+  relationType?: RelationDefinitionType,
+  currentWorkspaceMember?: CurrentWorkspaceMember,
+  label?: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.Is:
+      if (
+        relationType === RelationDefinitionType.MANY_TO_ONE ||
+        relationType === RelationDefinitionType.ONE_TO_ONE
+      ) {
+        if (label === 'Assignee') {
+          return JSON.parse(value).isCurrentWorkspaceMemberSelected
+            ? currentWorkspaceMember?.id
+            : undefined;
+        } else {
path: packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.ts
user: login: greptile-apps[bot]
body: style: Multiple JSON.parse() calls on same value string in computeValueFromFilterRelation could be optimized by parsing once

```suggestion
        if (label === 'Assignee') {
          const parsedValue = JSON.parse(value);
          return parsedValue.isCurrentWorkspaceMemberSelected
            ? currentWorkspaceMember?.id
            : undefined;
        } else {
```
created_at: 2025-04-24T11:55:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -33,7 +34,8 @@ export const RecordTitleCellFieldInput = ({
   if (!isFieldText(fieldDefinition) && !isFieldFullName(fieldDefinition)) {
     throw new Error('Field definition is not a text or full name field');
   }
path: packages/twenty-front/src/modules/object-record/record-title-cell/components/RecordTitleCellFieldInput.tsx
user: login: greptile-apps[bot]
body: logic: Error is thrown after hook call (openInlineCell), violating React hooks rules. Move error check before any hook usage.

```suggestion
  const { fieldDefinition } = useContext(FieldContext);

  if (!isFieldText(fieldDefinition) && !isFieldFullName(fieldDefinition)) {
    throw new Error('Field definition is not a text or full name field');
  }
  const { openInlineCell } = useInlineCell();
```
created_at: 2025-04-24T11:55:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -7,10 +7,118 @@ export type GetRecordFilterOperandsParams = {
   subFieldName?: string | null | undefined;
 };
 
+const emptyOperands = [
+  RecordFilterOperand.IsEmpty,
+  RecordFilterOperand.IsNotEmpty,
+] as const;
+
+const relationOperands = [
+  RecordFilterOperand.Is,
+  RecordFilterOperand.IsNot,
+] as const;
+
+type FilterOperandMap = {
+  [K in FilterableFieldType]: readonly RecordFilterOperand[];
+};
+
+export const FILTER_OPERANDS_MAP = {
+  TEXT: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  EMAILS: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  FULL_NAME: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  ADDRESS: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  LINKS: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  PHONES: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  CURRENCY: [
+    RecordFilterOperand.GreaterThan,
+    RecordFilterOperand.LessThan,
+    ...emptyOperands,
+  ],
+  NUMBER: [
+    RecordFilterOperand.GreaterThan,
+    RecordFilterOperand.LessThan,
+    ...emptyOperands,
+  ],
+  RAW_JSON: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  DATE_TIME: [
+    RecordFilterOperand.Is,
+    RecordFilterOperand.IsRelative,
+    RecordFilterOperand.IsInPast,
+    RecordFilterOperand.IsInFuture,
+    RecordFilterOperand.IsToday,
+    RecordFilterOperand.IsBefore,
+    RecordFilterOperand.IsAfter,
+    ...emptyOperands,
+  ],
+  DATE: [
+    RecordFilterOperand.Is,
+    RecordFilterOperand.IsRelative,
+    RecordFilterOperand.IsInPast,
+    RecordFilterOperand.IsInFuture,
+    RecordFilterOperand.IsToday,
+    RecordFilterOperand.IsBefore,
+    RecordFilterOperand.IsAfter,
+    ...emptyOperands,
+  ],
+  RATING: [
+    RecordFilterOperand.Is,
+    RecordFilterOperand.GreaterThan,
+    RecordFilterOperand.LessThan,
+    ...emptyOperands,
+  ],
+  RELATION: [...relationOperands, ...emptyOperands],
+  MULTI_SELECT: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  SELECT: [RecordFilterOperand.Is, RecordFilterOperand.IsNot, ...emptyOperands],
+  ACTOR: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  ARRAY: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  BOOLEAN: [RecordFilterOperand.Is],
+} as const;
+
+FILTER_OPERANDS_MAP as FilterOperandMap;
+
 export const getRecordFilterOperands = ({
   filterType,
   subFieldName,
-}: GetRecordFilterOperandsParams): RecordFilterOperand[] => {
+}: GetRecordFilterOperandsParams) => {
path: packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts
user: login: greptile-apps[bot]
body: logic: Return type RecordFilterOperand[] was removed. This could cause type inference issues in consuming code.
created_at: 2025-04-24T11:55:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -29,7 +137,7 @@ export const getRecordFilterOperands = ({
         RecordFilterOperand.Contains,
         RecordFilterOperand.DoesNotContain,
         ...emptyOperands,
-      ];
+      ] as const;
path: packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts
user: login: greptile-apps[bot]
body: style: Inconsistent use of 'as const' - some returns have it while others don't. This could lead to type inference issues.
created_at: 2025-04-24T11:55:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -110,6 +110,8 @@ export const ViewBarDetails = ({
 
   const { hasFiltersQueryParams } = useViewFromQueryParams();
 
+  //tododo here
path: packages/twenty-front/src/modules/views/components/ViewBarDetails.tsx
user: login: greptile-apps[bot]
body: style: Remove or address this TODO comment before merging

```suggestion

```
created_at: 2025-04-24T11:55:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -7,6 +7,114 @@ export type GetRecordFilterOperandsParams = {
   subFieldName?: string | null | undefined;
 };
 
+const emptyOperands = [
+  RecordFilterOperand.IsEmpty,
+  RecordFilterOperand.IsNotEmpty,
+] as const;
+
+const relationOperands = [
+  RecordFilterOperand.Is,
+  RecordFilterOperand.IsNot,
+] as const;
+
+type FilterOperandMap = {
+  [K in FilterableFieldType]: readonly RecordFilterOperand[];
+};
+
+export const FILTER_OPERANDS_MAP = {
+  TEXT: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  EMAILS: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  FULL_NAME: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  ADDRESS: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  LINKS: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  PHONES: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  CURRENCY: [
+    RecordFilterOperand.GreaterThan,
+    RecordFilterOperand.LessThan,
+    ...emptyOperands,
+  ],
+  NUMBER: [
+    RecordFilterOperand.GreaterThan,
+    RecordFilterOperand.LessThan,
+    ...emptyOperands,
+  ],
+  RAW_JSON: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  DATE_TIME: [
+    RecordFilterOperand.Is,
+    RecordFilterOperand.IsRelative,
+    RecordFilterOperand.IsInPast,
+    RecordFilterOperand.IsInFuture,
+    RecordFilterOperand.IsToday,
+    RecordFilterOperand.IsBefore,
+    RecordFilterOperand.IsAfter,
+    ...emptyOperands,
+  ],
+  DATE: [
+    RecordFilterOperand.Is,
+    RecordFilterOperand.IsRelative,
+    RecordFilterOperand.IsInPast,
+    RecordFilterOperand.IsInFuture,
+    RecordFilterOperand.IsToday,
+    RecordFilterOperand.IsBefore,
+    RecordFilterOperand.IsAfter,
+    ...emptyOperands,
+  ],
+  RATING: [
+    RecordFilterOperand.Is,
+    RecordFilterOperand.GreaterThan,
+    RecordFilterOperand.LessThan,
+    ...emptyOperands,
+  ],
+  RELATION: [...relationOperands, ...emptyOperands],
+  MULTI_SELECT: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  SELECT: [RecordFilterOperand.Is, RecordFilterOperand.IsNot, ...emptyOperands],
+  ACTOR: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  ARRAY: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  BOOLEAN: [RecordFilterOperand.Is],
+} as const;
+
+FILTER_OPERANDS_MAP as FilterOperandMap;
path: packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts
user: login: Devessier
body: I think this line is a typo and we can remove it. What do you think?
created_at: 2025-04-24T12:51:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -159,7 +159,7 @@ const resolveVariableDateViewFilterValueFromRelativeDate = (relativeDate: {
   }
 };
 
-const resolveVariableDateViewFilterValue = (value?: string | null) => {
+export const resolveVariableDateViewFilterValue = (value?: string | null) => {
path: packages/twenty-front/src/modules/views/view-filter-value/utils/resolveDateViewFilterValue.ts
user: login: Devessier
body: nitpick: do we still need to export this function? It doesn't seem to be used elsewhere in this PR.
created_at: 2025-04-24T12:54:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,285 @@
+import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState';
+import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
+import { isCompositeField } from '@/object-record/object-filter-dropdown/utils/isCompositeField';
+
+import {
+  RecordFilter,
+  RecordFilterInput,
+  RecordFilterToRecordInputOperand,
+} from '@/object-record/record-filter/types/RecordFilter';
+import { FILTER_OPERANDS_MAP } from '@/object-record/record-filter/utils/getRecordFilterOperands';
+import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
+import { assertUnreachable } from 'twenty-shared/utils';
+import { RelationDefinitionType } from '~/generated-metadata/graphql';
+
+export const buildValueFromFilter = ({
+  filter,
+  options,
+  relationType,
+  currentWorkspaceMember,
+  label,
+}: {
+  filter: RecordFilter;
+  options?: FieldMetadataItemOption[];
+  relationType?: RelationDefinitionType;
+  currentWorkspaceMember?: CurrentWorkspaceMember;
+  label?: string;
+}) => {
+  if (isCompositeField(filter.type)) {
+    return;
+  }
+
+  if (filter.type === 'RAW_JSON') {
path: packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.ts
user: login: etiennejouan
body: FieldMetadataType.RAW_JSON ? or filter.type can't accept it ?
created_at: 2025-04-24T13:11:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -7,6 +7,114 @@ export type GetRecordFilterOperandsParams = {
   subFieldName?: string | null | undefined;
 };
 
+const emptyOperands = [
+  RecordFilterOperand.IsEmpty,
+  RecordFilterOperand.IsNotEmpty,
+] as const;
+
+const relationOperands = [
+  RecordFilterOperand.Is,
+  RecordFilterOperand.IsNot,
+] as const;
+
+type FilterOperandMap = {
+  [K in FilterableFieldType]: readonly RecordFilterOperand[];
+};
+
+export const FILTER_OPERANDS_MAP = {
+  TEXT: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  EMAILS: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  FULL_NAME: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  ADDRESS: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  LINKS: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  PHONES: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  CURRENCY: [
+    RecordFilterOperand.GreaterThan,
+    RecordFilterOperand.LessThan,
+    ...emptyOperands,
+  ],
+  NUMBER: [
+    RecordFilterOperand.GreaterThan,
+    RecordFilterOperand.LessThan,
+    ...emptyOperands,
+  ],
+  RAW_JSON: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  DATE_TIME: [
+    RecordFilterOperand.Is,
+    RecordFilterOperand.IsRelative,
+    RecordFilterOperand.IsInPast,
+    RecordFilterOperand.IsInFuture,
+    RecordFilterOperand.IsToday,
+    RecordFilterOperand.IsBefore,
+    RecordFilterOperand.IsAfter,
+    ...emptyOperands,
+  ],
+  DATE: [
+    RecordFilterOperand.Is,
+    RecordFilterOperand.IsRelative,
+    RecordFilterOperand.IsInPast,
+    RecordFilterOperand.IsInFuture,
+    RecordFilterOperand.IsToday,
+    RecordFilterOperand.IsBefore,
+    RecordFilterOperand.IsAfter,
+    ...emptyOperands,
+  ],
+  RATING: [
+    RecordFilterOperand.Is,
+    RecordFilterOperand.GreaterThan,
+    RecordFilterOperand.LessThan,
+    ...emptyOperands,
+  ],
+  RELATION: [...relationOperands, ...emptyOperands],
+  MULTI_SELECT: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  SELECT: [RecordFilterOperand.Is, RecordFilterOperand.IsNot, ...emptyOperands],
+  ACTOR: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  ARRAY: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  BOOLEAN: [RecordFilterOperand.Is],
+} as const;
+
+FILTER_OPERANDS_MAP as FilterOperandMap;
path: packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts
user: login: etiennejouan
body: It's here for asserting type of FILTER_OPERANDS_MAP. 'satisfies' would be better, we have just discover it with @guillim. 

> as const as satisfies FilterOperandMap (line 114)
created_at: 2025-04-24T13:34:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,285 @@
+import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState';
+import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
+import { isCompositeField } from '@/object-record/object-filter-dropdown/utils/isCompositeField';
+
+import {
+  RecordFilter,
+  RecordFilterInput,
+  RecordFilterToRecordInputOperand,
+} from '@/object-record/record-filter/types/RecordFilter';
+import { FILTER_OPERANDS_MAP } from '@/object-record/record-filter/utils/getRecordFilterOperands';
+import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
+import { assertUnreachable } from 'twenty-shared/utils';
+import { RelationDefinitionType } from '~/generated-metadata/graphql';
+
+export const buildValueFromFilter = ({
+  filter,
+  options,
+  relationType,
+  currentWorkspaceMember,
+  label,
+}: {
+  filter: RecordFilter;
+  options?: FieldMetadataItemOption[];
+  relationType?: RelationDefinitionType;
+  currentWorkspaceMember?: CurrentWorkspaceMember;
+  label?: string;
+}) => {
+  if (isCompositeField(filter.type)) {
+    return;
+  }
+
+  if (filter.type === 'RAW_JSON') {
+    return;
+  }
+
+  const operands = FILTER_OPERANDS_MAP[filter.type];
+  if (!operands.some((operand) => operand === filter.operand)) {
+    throw new Error('Operand not supported for this field type');
+  }
+
+  switch (filter.type) {
+    case 'TEXT': {
path: packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.ts
user: login: etiennejouan
body: Same here, we can't use an enum ?
created_at: 2025-04-24T13:36:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -33,7 +34,8 @@ export const RecordTitleCellFieldInput = ({
   if (!isFieldText(fieldDefinition) && !isFieldFullName(fieldDefinition)) {
     throw new Error('Field definition is not a text or full name field');
   }
-
+  const { openInlineCell } = useInlineCell();
+  openInlineCell();
path: packages/twenty-front/src/modules/object-record/record-title-cell/components/RecordTitleCellFieldInput.tsx
user: login: etiennejouan
body: As we see it, to remove
created_at: 2025-04-24T13:40:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,285 @@
+import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState';
+import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
+import { isCompositeField } from '@/object-record/object-filter-dropdown/utils/isCompositeField';
+
+import {
+  RecordFilter,
+  RecordFilterInput,
+  RecordFilterToRecordInputOperand,
+} from '@/object-record/record-filter/types/RecordFilter';
+import { FILTER_OPERANDS_MAP } from '@/object-record/record-filter/utils/getRecordFilterOperands';
+import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
+import { assertUnreachable } from 'twenty-shared/utils';
+import { RelationDefinitionType } from '~/generated-metadata/graphql';
+
+export const buildValueFromFilter = ({
+  filter,
+  options,
+  relationType,
+  currentWorkspaceMember,
+  label,
+}: {
+  filter: RecordFilter;
+  options?: FieldMetadataItemOption[];
+  relationType?: RelationDefinitionType;
+  currentWorkspaceMember?: CurrentWorkspaceMember;
+  label?: string;
+}) => {
+  if (isCompositeField(filter.type)) {
+    return;
+  }
+
+  if (filter.type === 'RAW_JSON') {
+    return;
+  }
+
+  const operands = FILTER_OPERANDS_MAP[filter.type];
+  if (!operands.some((operand) => operand === filter.operand)) {
+    throw new Error('Operand not supported for this field type');
+  }
+
+  switch (filter.type) {
+    case 'TEXT': {
+      return computeValueFromFilterText(
+        filter.operand as RecordFilterInput<'TEXT'>['operand'],
+        filter.value,
+      );
+    }
+    case 'RATING':
+      return computeValueFromFilterRating(
+        filter.operand as RecordFilterInput<'RATING'>['operand'],
+        filter.value,
+        options,
+      );
+    case 'DATE_TIME':
+    case 'DATE':
+      return computeValueFromFilterDate(
+        filter.operand as RecordFilterInput<'DATE_TIME'>['operand'],
+        filter.value,
+      );
+    case 'NUMBER':
+      return computeValueFromFilterNumber(
+        filter.operand as RecordFilterInput<'NUMBER'>['operand'],
+        filter.value,
+      );
+    case 'BOOLEAN':
+      return computeValueFromFilterBoolean(
+        filter.operand as RecordFilterInput<'BOOLEAN'>['operand'],
+        filter.value,
+      );
+    case 'ARRAY':
+      return computeValueFromFilterArray(
+        filter.operand as RecordFilterInput<'ARRAY'>['operand'],
+        filter.value,
+      );
+    case 'SELECT':
+      return computeValueFromFilterSelect(
+        filter.operand as RecordFilterInput<'SELECT'>['operand'],
+        filter.value,
+        options,
+      );
+    case 'MULTI_SELECT':
+      return computeValueFromFilterMultiSelect(
+        filter.operand as RecordFilterInput<'MULTI_SELECT'>['operand'],
+        filter.value,
+      );
+    case 'RELATION': {
+      return computeValueFromFilterRelation(
+        filter.operand as RecordFilterInput<'RELATION'>['operand'],
+        filter.value,
+        relationType,
+        currentWorkspaceMember,
+        label,
+      );
+    }
+    default:
+      assertUnreachable(filter.type);
+  }
+};
+
+const computeValueFromFilterText = (
+  operand: RecordFilterToRecordInputOperand<'TEXT'>,
+  value: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.Contains:
+      return value;
+    case ViewFilterOperand.IsNotEmpty:
+      return value;
+    case ViewFilterOperand.IsEmpty:
+    case ViewFilterOperand.DoesNotContain:
+      return undefined;
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterDate = (
+  operand: RecordFilterToRecordInputOperand<'DATE_TIME'>,
+  value: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.Is:
+    case ViewFilterOperand.IsNotEmpty:
+    case ViewFilterOperand.IsAfter:
+    case ViewFilterOperand.IsBefore:
+    case ViewFilterOperand.IsInPast:
+    case ViewFilterOperand.IsInFuture:
path: packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.ts
user: login: etiennejouan
body: IsInPast is not working, should be with new Date() ?
created_at: 2025-04-24T13:51:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -14,3 +15,13 @@ export type RecordFilter = {
   label: string;
   subFieldName?: string | null | undefined;
 };
+
+export type RecordFilterInput<
path: packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilter.ts
user: login: prastoin
body: ***Suggestion:*** Could be completely removed
created_at: 2025-04-24T13:53:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -14,3 +15,13 @@ export type RecordFilter = {
   label: string;
   subFieldName?: string | null | undefined;
 };
+
+export type RecordFilterInput<
+  T extends FilterableFieldType = FilterableFieldType,
+> = Pick<RecordFilter, 'value'> & {
+  type: T;
+  operand: (typeof FILTER_OPERANDS_MAP)[T][number];
+};
+
+export type RecordFilterToRecordInputOperand<T extends FilterableFieldType> =
path: packages/twenty-front/src/modules/object-record/record-filter/types/RecordFilter.ts
user: login: prastoin
body: ***Nitpick:*** Should be moved within `getRecordFilterOperands` file
created_at: 2025-04-24T13:54:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,285 @@
+import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState';
+import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
+import { isCompositeField } from '@/object-record/object-filter-dropdown/utils/isCompositeField';
+
+import {
+  RecordFilter,
+  RecordFilterInput,
+  RecordFilterToRecordInputOperand,
+} from '@/object-record/record-filter/types/RecordFilter';
+import { FILTER_OPERANDS_MAP } from '@/object-record/record-filter/utils/getRecordFilterOperands';
+import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
+import { assertUnreachable } from 'twenty-shared/utils';
+import { RelationDefinitionType } from '~/generated-metadata/graphql';
+
+export const buildValueFromFilter = ({
+  filter,
+  options,
+  relationType,
+  currentWorkspaceMember,
+  label,
+}: {
+  filter: RecordFilter;
+  options?: FieldMetadataItemOption[];
+  relationType?: RelationDefinitionType;
+  currentWorkspaceMember?: CurrentWorkspaceMember;
+  label?: string;
+}) => {
+  if (isCompositeField(filter.type)) {
+    return;
+  }
+
+  if (filter.type === 'RAW_JSON') {
+    return;
+  }
+
+  const operands = FILTER_OPERANDS_MAP[filter.type];
+  if (!operands.some((operand) => operand === filter.operand)) {
+    throw new Error('Operand not supported for this field type');
+  }
+
+  switch (filter.type) {
+    case 'TEXT': {
+      return computeValueFromFilterText(
+        filter.operand as RecordFilterInput<'TEXT'>['operand'],
+        filter.value,
+      );
+    }
+    case 'RATING':
+      return computeValueFromFilterRating(
+        filter.operand as RecordFilterInput<'RATING'>['operand'],
+        filter.value,
+        options,
+      );
+    case 'DATE_TIME':
+    case 'DATE':
+      return computeValueFromFilterDate(
+        filter.operand as RecordFilterInput<'DATE_TIME'>['operand'],
+        filter.value,
+      );
+    case 'NUMBER':
+      return computeValueFromFilterNumber(
+        filter.operand as RecordFilterInput<'NUMBER'>['operand'],
+        filter.value,
+      );
+    case 'BOOLEAN':
+      return computeValueFromFilterBoolean(
+        filter.operand as RecordFilterInput<'BOOLEAN'>['operand'],
+        filter.value,
+      );
+    case 'ARRAY':
+      return computeValueFromFilterArray(
+        filter.operand as RecordFilterInput<'ARRAY'>['operand'],
+        filter.value,
+      );
+    case 'SELECT':
+      return computeValueFromFilterSelect(
+        filter.operand as RecordFilterInput<'SELECT'>['operand'],
+        filter.value,
+        options,
+      );
+    case 'MULTI_SELECT':
+      return computeValueFromFilterMultiSelect(
+        filter.operand as RecordFilterInput<'MULTI_SELECT'>['operand'],
+        filter.value,
+      );
+    case 'RELATION': {
+      return computeValueFromFilterRelation(
+        filter.operand as RecordFilterInput<'RELATION'>['operand'],
+        filter.value,
+        relationType,
+        currentWorkspaceMember,
+        label,
+      );
+    }
+    default:
+      assertUnreachable(filter.type);
+  }
+};
+
+const computeValueFromFilterText = (
+  operand: RecordFilterToRecordInputOperand<'TEXT'>,
+  value: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.Contains:
+      return value;
+    case ViewFilterOperand.IsNotEmpty:
+      return value;
+    case ViewFilterOperand.IsEmpty:
+    case ViewFilterOperand.DoesNotContain:
+      return undefined;
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterDate = (
+  operand: RecordFilterToRecordInputOperand<'DATE_TIME'>,
+  value: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.Is:
+    case ViewFilterOperand.IsNotEmpty:
+    case ViewFilterOperand.IsAfter:
+    case ViewFilterOperand.IsBefore:
+    case ViewFilterOperand.IsInPast:
+    case ViewFilterOperand.IsInFuture:
+      return new Date(value);
+    case ViewFilterOperand.IsToday:
+    case ViewFilterOperand.IsRelative:
+      return new Date();
+    case ViewFilterOperand.IsEmpty:
+      return undefined;
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterNumber = (
+  operand: RecordFilterToRecordInputOperand<'NUMBER'>,
+  value: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.GreaterThan:
+      return Number(value) + 1;
+    case ViewFilterOperand.LessThan:
+      return Number(value) - 1;
+    case ViewFilterOperand.IsNotEmpty:
+      return Number(value);
+    case ViewFilterOperand.IsEmpty:
+      return undefined;
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterBoolean = (
+  operand: RecordFilterToRecordInputOperand<'BOOLEAN'>,
+  value: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.Is:
+      return value === 'true';
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterArray = (
+  operand: RecordFilterToRecordInputOperand<'ARRAY'>,
+  value: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.Contains:
+    case ViewFilterOperand.IsNotEmpty:
+      return value;
+    case ViewFilterOperand.DoesNotContain:
+    case ViewFilterOperand.IsEmpty:
+      return undefined;
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterRating = (
+  operand: RecordFilterToRecordInputOperand<'RATING'>,
+  value: string,
+  options?: FieldMetadataItemOption[],
+) => {
+  const option = options?.find((option) => option.label === value);
+  if (!option) {
+    return undefined;
+  }
+
+  switch (operand) {
+    case ViewFilterOperand.Is:
+    case ViewFilterOperand.IsNotEmpty:
+    case ViewFilterOperand.GreaterThan:
+    case ViewFilterOperand.LessThan:
path: packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.ts
user: login: etiennejouan
body: Not working with GreaterThan and LessThan
created_at: 2025-04-24T13:55:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -7,6 +7,114 @@ export type GetRecordFilterOperandsParams = {
   subFieldName?: string | null | undefined;
 };
 
+const emptyOperands = [
+  RecordFilterOperand.IsEmpty,
+  RecordFilterOperand.IsNotEmpty,
+] as const;
+
+const relationOperands = [
+  RecordFilterOperand.Is,
+  RecordFilterOperand.IsNot,
+] as const;
+
+type FilterOperandMap = {
+  [K in FilterableFieldType]: readonly RecordFilterOperand[];
+};
+
+export const FILTER_OPERANDS_MAP = {
+  TEXT: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  EMAILS: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  FULL_NAME: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  ADDRESS: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  LINKS: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  PHONES: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  CURRENCY: [
+    RecordFilterOperand.GreaterThan,
+    RecordFilterOperand.LessThan,
+    ...emptyOperands,
+  ],
+  NUMBER: [
+    RecordFilterOperand.GreaterThan,
+    RecordFilterOperand.LessThan,
+    ...emptyOperands,
+  ],
+  RAW_JSON: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  DATE_TIME: [
+    RecordFilterOperand.Is,
+    RecordFilterOperand.IsRelative,
+    RecordFilterOperand.IsInPast,
+    RecordFilterOperand.IsInFuture,
+    RecordFilterOperand.IsToday,
+    RecordFilterOperand.IsBefore,
+    RecordFilterOperand.IsAfter,
+    ...emptyOperands,
+  ],
+  DATE: [
+    RecordFilterOperand.Is,
+    RecordFilterOperand.IsRelative,
+    RecordFilterOperand.IsInPast,
+    RecordFilterOperand.IsInFuture,
+    RecordFilterOperand.IsToday,
+    RecordFilterOperand.IsBefore,
+    RecordFilterOperand.IsAfter,
+    ...emptyOperands,
+  ],
+  RATING: [
+    RecordFilterOperand.Is,
+    RecordFilterOperand.GreaterThan,
+    RecordFilterOperand.LessThan,
+    ...emptyOperands,
+  ],
+  RELATION: [...relationOperands, ...emptyOperands],
+  MULTI_SELECT: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  SELECT: [RecordFilterOperand.Is, RecordFilterOperand.IsNot, ...emptyOperands],
+  ACTOR: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  ARRAY: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  BOOLEAN: [RecordFilterOperand.Is],
+} as const;
+
+FILTER_OPERANDS_MAP as FilterOperandMap;
+
 export const getRecordFilterOperands = ({
path: packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts
user: login: prastoin
body: ***Question:*** Could refactor this method to consume `FILTER_OPERANDS_MAP` in order to avoid duplicating its values ? Or just deprecates it
created_at: 2025-04-24T13:56:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -7,6 +7,114 @@ export type GetRecordFilterOperandsParams = {
   subFieldName?: string | null | undefined;
 };
 
+const emptyOperands = [
+  RecordFilterOperand.IsEmpty,
+  RecordFilterOperand.IsNotEmpty,
+] as const;
+
+const relationOperands = [
+  RecordFilterOperand.Is,
+  RecordFilterOperand.IsNot,
+] as const;
+
+type FilterOperandMap = {
+  [K in FilterableFieldType]: readonly RecordFilterOperand[];
+};
+
+export const FILTER_OPERANDS_MAP = {
+  TEXT: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  EMAILS: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  FULL_NAME: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  ADDRESS: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  LINKS: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  PHONES: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  CURRENCY: [
+    RecordFilterOperand.GreaterThan,
+    RecordFilterOperand.LessThan,
+    ...emptyOperands,
+  ],
+  NUMBER: [
+    RecordFilterOperand.GreaterThan,
+    RecordFilterOperand.LessThan,
+    ...emptyOperands,
+  ],
+  RAW_JSON: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  DATE_TIME: [
+    RecordFilterOperand.Is,
+    RecordFilterOperand.IsRelative,
+    RecordFilterOperand.IsInPast,
+    RecordFilterOperand.IsInFuture,
+    RecordFilterOperand.IsToday,
+    RecordFilterOperand.IsBefore,
+    RecordFilterOperand.IsAfter,
+    ...emptyOperands,
+  ],
+  DATE: [
+    RecordFilterOperand.Is,
+    RecordFilterOperand.IsRelative,
+    RecordFilterOperand.IsInPast,
+    RecordFilterOperand.IsInFuture,
+    RecordFilterOperand.IsToday,
+    RecordFilterOperand.IsBefore,
+    RecordFilterOperand.IsAfter,
+    ...emptyOperands,
+  ],
+  RATING: [
+    RecordFilterOperand.Is,
+    RecordFilterOperand.GreaterThan,
+    RecordFilterOperand.LessThan,
+    ...emptyOperands,
+  ],
+  RELATION: [...relationOperands, ...emptyOperands],
+  MULTI_SELECT: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  SELECT: [RecordFilterOperand.Is, RecordFilterOperand.IsNot, ...emptyOperands],
+  ACTOR: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  ARRAY: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  BOOLEAN: [RecordFilterOperand.Is],
+} as const;
path: packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts
user: login: prastoin
body: ***Nitpick:*** Should add a `satisfies` statement in order to strictly type the const declaration, for example:
```ts
as const satisfies Record<TranspilableRecordFilterToRecordInputType, Array<RecordFilterOperand>>
```
created_at: 2025-04-24T13:57:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,54 @@
+import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
+import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
+import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
+import { buildValueFromFilter } from '@/object-record/record-table/utils/buildRecordInputFromFilter';
+
+import { ObjectRecord } from '@/object-record/types/ObjectRecord';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+import { useRecoilValue } from 'recoil';
+import { isDefined } from 'twenty-shared/utils';
+
+export const useBuildRecordInputFromFilters = ({
+  objectMetadataItem,
+}: {
+  objectMetadataItem: ObjectMetadataItem;
+}) => {
+  // we might need to build a recoil callback for better performance
+  const currentRecordFilters = useRecoilComponentValueV2(
+    currentRecordFiltersComponentState,
+  );
+
+  const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
+
+  const buildRecordInputFromFilters = (): Partial<ObjectRecord> => {
+    const recordInput: Partial<ObjectRecord> = {};
+
+    currentRecordFilters.forEach((filter) => {
+      const fieldMetadataItem = objectMetadataItem.fields.find(
+        (field) => field.id === filter.fieldMetadataId,
+      );
+
+      if (!isDefined(fieldMetadataItem)) {
+        return;
+      }
+      if (fieldMetadataItem.type === 'RELATION') {
+        recordInput[`${fieldMetadataItem.name}Id`] = buildValueFromFilter({
+          filter,
+          options: fieldMetadataItem.options ?? undefined,
+          relationType: fieldMetadataItem.relationDefinition?.direction,
+          currentWorkspaceMember: currentWorkspaceMember ?? undefined,
+          label: filter.label,
+        });
+      } else {
path: packages/twenty-front/src/modules/object-record/record-table/hooks/useBuildRecordInputFromFilters.ts
user: login: prastoin
body: ***Nitpick:*** Could early return
created_at: 2025-04-24T13:58:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -159,7 +159,7 @@ const resolveVariableDateViewFilterValueFromRelativeDate = (relativeDate: {
   }
 };
 
-const resolveVariableDateViewFilterValue = (value?: string | null) => {
+export const resolveVariableDateViewFilterValue = (value?: string | null) => {
path: packages/twenty-front/src/modules/views/view-filter-value/utils/resolveDateViewFilterValue.ts
user: login: prastoin
body: ***Nitpick:*** `export` statement could be removed
created_at: 2025-04-24T14:04:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,54 @@
+import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
+import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
+import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
+import { buildValueFromFilter } from '@/object-record/record-table/utils/buildRecordInputFromFilter';
+
+import { ObjectRecord } from '@/object-record/types/ObjectRecord';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+import { useRecoilValue } from 'recoil';
+import { isDefined } from 'twenty-shared/utils';
+
+export const useBuildRecordInputFromFilters = ({
+  objectMetadataItem,
+}: {
+  objectMetadataItem: ObjectMetadataItem;
+}) => {
+  // we might need to build a recoil callback for better performance
+  const currentRecordFilters = useRecoilComponentValueV2(
+    currentRecordFiltersComponentState,
+  );
+
+  const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
+
+  const buildRecordInputFromFilters = (): Partial<ObjectRecord> => {
+    const recordInput: Partial<ObjectRecord> = {};
+
+    currentRecordFilters.forEach((filter) => {
+      const fieldMetadataItem = objectMetadataItem.fields.find(
+        (field) => field.id === filter.fieldMetadataId,
+      );
+
+      if (!isDefined(fieldMetadataItem)) {
+        return;
+      }
+      if (fieldMetadataItem.type === 'RELATION') {
+        recordInput[`${fieldMetadataItem.name}Id`] = buildValueFromFilter({
path: packages/twenty-front/src/modules/object-record/record-table/hooks/useBuildRecordInputFromFilters.ts
user: login: prastoin
body: ***Question:*** Is it expected for undefined values to be set within the `recordInput` ?
I'm wondering if we wanna prefer avoid defining `key: undefined` in the `recordInput` rather than just no keys at all. `createOneRecord` kinda iterates over keys within the input payload that could result in unwanted behavior :thinking:
created_at: 2025-04-24T14:07:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,54 @@
+import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
+import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
+import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
+import { buildValueFromFilter } from '@/object-record/record-table/utils/buildRecordInputFromFilter';
+
+import { ObjectRecord } from '@/object-record/types/ObjectRecord';
+import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
+import { useRecoilValue } from 'recoil';
+import { isDefined } from 'twenty-shared/utils';
+
+export const useBuildRecordInputFromFilters = ({
+  objectMetadataItem,
+}: {
+  objectMetadataItem: ObjectMetadataItem;
+}) => {
+  // we might need to build a recoil callback for better performance
+  const currentRecordFilters = useRecoilComponentValueV2(
+    currentRecordFiltersComponentState,
+  );
+
+  const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
+
+  const buildRecordInputFromFilters = (): Partial<ObjectRecord> => {
+    const recordInput: Partial<ObjectRecord> = {};
+
+    currentRecordFilters.forEach((filter) => {
path: packages/twenty-front/src/modules/object-record/record-table/hooks/useBuildRecordInputFromFilters.ts
user: login: prastoin
body: ***Very subjective nitpick:*** Feel like we could either use a `reduce` or a `map` then a `Object.fromEntries` or a for loop, unless I'm mistaken haven't seen to many pattern with `forEach` in the codebase.
created_at: 2025-04-24T14:09:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,285 @@
+import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState';
+import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
+import { isCompositeField } from '@/object-record/object-filter-dropdown/utils/isCompositeField';
+
+import {
+  RecordFilter,
+  RecordFilterInput,
+  RecordFilterToRecordInputOperand,
+} from '@/object-record/record-filter/types/RecordFilter';
+import { FILTER_OPERANDS_MAP } from '@/object-record/record-filter/utils/getRecordFilterOperands';
+import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
+import { assertUnreachable } from 'twenty-shared/utils';
+import { RelationDefinitionType } from '~/generated-metadata/graphql';
+
+export const buildValueFromFilter = ({
+  filter,
+  options,
+  relationType,
+  currentWorkspaceMember,
+  label,
+}: {
+  filter: RecordFilter;
+  options?: FieldMetadataItemOption[];
+  relationType?: RelationDefinitionType;
+  currentWorkspaceMember?: CurrentWorkspaceMember;
+  label?: string;
+}) => {
+  if (isCompositeField(filter.type)) {
+    return;
+  }
+
+  if (filter.type === 'RAW_JSON') {
+    return;
+  }
+
+  const operands = FILTER_OPERANDS_MAP[filter.type];
+  if (!operands.some((operand) => operand === filter.operand)) {
+    throw new Error('Operand not supported for this field type');
+  }
+
+  switch (filter.type) {
+    case 'TEXT': {
path: packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.ts
user: login: prastoin
body: It's strictly typed anw but yep we could, we should also create a subset of the `FilterableFieldType` describing what filter types are available to recordInput transpilation
created_at: 2025-04-24T14:12:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -7,6 +7,114 @@ export type GetRecordFilterOperandsParams = {
   subFieldName?: string | null | undefined;
 };
 
+const emptyOperands = [
+  RecordFilterOperand.IsEmpty,
+  RecordFilterOperand.IsNotEmpty,
+] as const;
+
+const relationOperands = [
+  RecordFilterOperand.Is,
+  RecordFilterOperand.IsNot,
+] as const;
+
+type FilterOperandMap = {
+  [K in FilterableFieldType]: readonly RecordFilterOperand[];
+};
+
+export const FILTER_OPERANDS_MAP = {
+  TEXT: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  EMAILS: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  FULL_NAME: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  ADDRESS: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  LINKS: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  PHONES: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  CURRENCY: [
+    RecordFilterOperand.GreaterThan,
+    RecordFilterOperand.LessThan,
+    ...emptyOperands,
+  ],
+  NUMBER: [
+    RecordFilterOperand.GreaterThan,
+    RecordFilterOperand.LessThan,
+    ...emptyOperands,
+  ],
+  RAW_JSON: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  DATE_TIME: [
+    RecordFilterOperand.Is,
+    RecordFilterOperand.IsRelative,
+    RecordFilterOperand.IsInPast,
+    RecordFilterOperand.IsInFuture,
+    RecordFilterOperand.IsToday,
+    RecordFilterOperand.IsBefore,
+    RecordFilterOperand.IsAfter,
+    ...emptyOperands,
+  ],
+  DATE: [
+    RecordFilterOperand.Is,
+    RecordFilterOperand.IsRelative,
+    RecordFilterOperand.IsInPast,
+    RecordFilterOperand.IsInFuture,
+    RecordFilterOperand.IsToday,
+    RecordFilterOperand.IsBefore,
+    RecordFilterOperand.IsAfter,
+    ...emptyOperands,
+  ],
+  RATING: [
+    RecordFilterOperand.Is,
+    RecordFilterOperand.GreaterThan,
+    RecordFilterOperand.LessThan,
+    ...emptyOperands,
+  ],
+  RELATION: [...relationOperands, ...emptyOperands],
+  MULTI_SELECT: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  SELECT: [RecordFilterOperand.Is, RecordFilterOperand.IsNot, ...emptyOperands],
+  ACTOR: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  ARRAY: [
+    RecordFilterOperand.Contains,
+    RecordFilterOperand.DoesNotContain,
+    ...emptyOperands,
+  ],
+  BOOLEAN: [RecordFilterOperand.Is],
+} as const;
+
+FILTER_OPERANDS_MAP as FilterOperandMap;
path: packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts
user: login: prastoin
body: Using `as` does not do any assertion it forces the cast to the given type
created_at: 2025-04-24T14:13:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,285 @@
+import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState';
+import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
+import { isCompositeField } from '@/object-record/object-filter-dropdown/utils/isCompositeField';
+
+import {
+  RecordFilter,
+  RecordFilterInput,
+  RecordFilterToRecordInputOperand,
+} from '@/object-record/record-filter/types/RecordFilter';
+import { FILTER_OPERANDS_MAP } from '@/object-record/record-filter/utils/getRecordFilterOperands';
+import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
+import { assertUnreachable } from 'twenty-shared/utils';
+import { RelationDefinitionType } from '~/generated-metadata/graphql';
+
+export const buildValueFromFilter = ({
+  filter,
+  options,
+  relationType,
+  currentWorkspaceMember,
+  label,
+}: {
+  filter: RecordFilter;
+  options?: FieldMetadataItemOption[];
+  relationType?: RelationDefinitionType;
+  currentWorkspaceMember?: CurrentWorkspaceMember;
+  label?: string;
+}) => {
+  if (isCompositeField(filter.type)) {
+    return;
+  }
+
+  if (filter.type === 'RAW_JSON') {
+    return;
+  }
+
+  const operands = FILTER_OPERANDS_MAP[filter.type];
+  if (!operands.some((operand) => operand === filter.operand)) {
+    throw new Error('Operand not supported for this field type');
+  }
+
+  switch (filter.type) {
+    case 'TEXT': {
+      return computeValueFromFilterText(
+        filter.operand as RecordFilterInput<'TEXT'>['operand'],
+        filter.value,
+      );
+    }
+    case 'RATING':
+      return computeValueFromFilterRating(
+        filter.operand as RecordFilterInput<'RATING'>['operand'],
+        filter.value,
+        options,
+      );
+    case 'DATE_TIME':
+    case 'DATE':
+      return computeValueFromFilterDate(
+        filter.operand as RecordFilterInput<'DATE_TIME'>['operand'],
+        filter.value,
+      );
+    case 'NUMBER':
+      return computeValueFromFilterNumber(
+        filter.operand as RecordFilterInput<'NUMBER'>['operand'],
+        filter.value,
+      );
+    case 'BOOLEAN':
+      return computeValueFromFilterBoolean(
+        filter.operand as RecordFilterInput<'BOOLEAN'>['operand'],
+        filter.value,
+      );
+    case 'ARRAY':
+      return computeValueFromFilterArray(
+        filter.operand as RecordFilterInput<'ARRAY'>['operand'],
+        filter.value,
+      );
+    case 'SELECT':
+      return computeValueFromFilterSelect(
+        filter.operand as RecordFilterInput<'SELECT'>['operand'],
+        filter.value,
+        options,
+      );
+    case 'MULTI_SELECT':
+      return computeValueFromFilterMultiSelect(
+        filter.operand as RecordFilterInput<'MULTI_SELECT'>['operand'],
+        filter.value,
+      );
+    case 'RELATION': {
+      return computeValueFromFilterRelation(
+        filter.operand as RecordFilterInput<'RELATION'>['operand'],
+        filter.value,
+        relationType,
+        currentWorkspaceMember,
+        label,
+      );
+    }
+    default:
+      assertUnreachable(filter.type);
+  }
+};
+
+const computeValueFromFilterText = (
+  operand: RecordFilterToRecordInputOperand<'TEXT'>,
+  value: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.Contains:
+      return value;
+    case ViewFilterOperand.IsNotEmpty:
+      return value;
+    case ViewFilterOperand.IsEmpty:
+    case ViewFilterOperand.DoesNotContain:
+      return undefined;
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterDate = (
+  operand: RecordFilterToRecordInputOperand<'DATE_TIME'>,
+  value: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.Is:
+    case ViewFilterOperand.IsNotEmpty:
+    case ViewFilterOperand.IsAfter:
+    case ViewFilterOperand.IsBefore:
+    case ViewFilterOperand.IsInPast:
+    case ViewFilterOperand.IsInFuture:
+      return new Date(value);
+    case ViewFilterOperand.IsToday:
+    case ViewFilterOperand.IsRelative:
+      return new Date();
+    case ViewFilterOperand.IsEmpty:
+      return undefined;
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterNumber = (
+  operand: RecordFilterToRecordInputOperand<'NUMBER'>,
+  value: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.GreaterThan:
+      return Number(value) + 1;
+    case ViewFilterOperand.LessThan:
+      return Number(value) - 1;
+    case ViewFilterOperand.IsNotEmpty:
+      return Number(value);
+    case ViewFilterOperand.IsEmpty:
+      return undefined;
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterBoolean = (
+  operand: RecordFilterToRecordInputOperand<'BOOLEAN'>,
+  value: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.Is:
+      return value === 'true';
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterArray = (
+  operand: RecordFilterToRecordInputOperand<'ARRAY'>,
+  value: string,
+) => {
+  switch (operand) {
+    case ViewFilterOperand.Contains:
+    case ViewFilterOperand.IsNotEmpty:
+      return value;
+    case ViewFilterOperand.DoesNotContain:
+    case ViewFilterOperand.IsEmpty:
+      return undefined;
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterRating = (
+  operand: RecordFilterToRecordInputOperand<'RATING'>,
+  value: string,
+  options?: FieldMetadataItemOption[],
+) => {
+  const option = options?.find((option) => option.label === value);
+  if (!option) {
+    return undefined;
+  }
+
+  switch (operand) {
+    case ViewFilterOperand.Is:
+    case ViewFilterOperand.IsNotEmpty:
+    case ViewFilterOperand.GreaterThan:
+    case ViewFilterOperand.LessThan:
+      return option.value;
+    case ViewFilterOperand.IsEmpty:
+      return undefined;
+    default:
+      assertUnreachable(operand);
+  }
+};
+
+const computeValueFromFilterSelect = (
+  operand: RecordFilterToRecordInputOperand<'SELECT'>,
+  value: string,
+  options?: FieldMetadataItemOption[],
+) => {
+  switch (operand) {
+    case ViewFilterOperand.Is:
+    case ViewFilterOperand.IsNotEmpty:
+      try {
+        const valueParsed = JSON.parse(value)?.[0];
path: packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.ts
user: login: prastoin
body: ***Suggestion:*** Could use `parseJson` Twenty's utils ( applicable to each JSON.parse calls )
created_at: 2025-04-24T14:15:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -159,7 +159,7 @@ const resolveVariableDateViewFilterValueFromRelativeDate = (relativeDate: {
   }
 };
 
-const resolveVariableDateViewFilterValue = (value?: string | null) => {
+export const resolveVariableDateViewFilterValue = (value?: string | null) => {
path: packages/twenty-front/src/modules/views/view-filter-value/utils/resolveDateViewFilterValue.ts
user: login: guillim
body: indeed
created_at: 2025-04-24T14:30:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,285 @@
+import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState';
+import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
+import { isCompositeField } from '@/object-record/object-filter-dropdown/utils/isCompositeField';
+
+import {
+  RecordFilter,
+  RecordFilterInput,
+  RecordFilterToRecordInputOperand,
+} from '@/object-record/record-filter/types/RecordFilter';
+import { FILTER_OPERANDS_MAP } from '@/object-record/record-filter/utils/getRecordFilterOperands';
+import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
+import { assertUnreachable } from 'twenty-shared/utils';
+import { RelationDefinitionType } from '~/generated-metadata/graphql';
+
+export const buildValueFromFilter = ({
+  filter,
+  options,
+  relationType,
+  currentWorkspaceMember,
+  label,
+}: {
+  filter: RecordFilter;
+  options?: FieldMetadataItemOption[];
+  relationType?: RelationDefinitionType;
+  currentWorkspaceMember?: CurrentWorkspaceMember;
+  label?: string;
+}) => {
+  if (isCompositeField(filter.type)) {
+    return;
+  }
+
+  if (filter.type === 'RAW_JSON') {
path: packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.ts
user: login: guillim
body: i don't see any filters of type RAW_JSON when using the app. I guess it's a "metier" choice
created_at: 2025-04-24T14:54:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,285 @@
+import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState';
+import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
+import { isCompositeField } from '@/object-record/object-filter-dropdown/utils/isCompositeField';
+
+import {
+  RecordFilter,
+  RecordFilterInput,
+  RecordFilterToRecordInputOperand,
+} from '@/object-record/record-filter/types/RecordFilter';
+import { FILTER_OPERANDS_MAP } from '@/object-record/record-filter/utils/getRecordFilterOperands';
+import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
+import { assertUnreachable } from 'twenty-shared/utils';
+import { RelationDefinitionType } from '~/generated-metadata/graphql';
+
+export const buildValueFromFilter = ({
+  filter,
+  options,
+  relationType,
+  currentWorkspaceMember,
+  label,
+}: {
+  filter: RecordFilter;
+  options?: FieldMetadataItemOption[];
+  relationType?: RelationDefinitionType;
+  currentWorkspaceMember?: CurrentWorkspaceMember;
+  label?: string;
+}) => {
+  if (isCompositeField(filter.type)) {
+    return;
+  }
+
+  if (filter.type === 'RAW_JSON') {
+    return;
+  }
+
+  const operands = FILTER_OPERANDS_MAP[filter.type];
+  if (!operands.some((operand) => operand === filter.operand)) {
+    throw new Error('Operand not supported for this field type');
+  }
+
+  switch (filter.type) {
+    case 'TEXT': {
path: packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.ts
user: login: guillim
body: TBH I am a bit on a rush for the release and since It does not look trivial to me, i am going to skip this one
created_at: 2025-04-24T15:13:25Z
author_association: CONTRIBUTOR

PR comments:
user: login: lucasbordeau
created_at: 2025-04-25T10:51:27Z
body: @Bonapara Does it make sense from a product perspective to try to guess values from a "> 10k euros" or a "is after today" that seems awkward from a user perspective, why don't we focus on the values we can easily guess (relations, options, etc.), and show a pop up or some UX pattern to warn the user, or maybe keep the record in a special state on top of the list, in a temporary "out of filtering" group ? Overall this seems like a confusing route to take to try to guess for every field type and that will lead to awkward code.

@prastoin Each degree of complexity we add on broadly used types like RecordFilter should be debated because this can burden us more than it helps on the long run (which can be counterintuitive). Having stricter typing is not necessarily good in all cases, we shouldn't be dogmatic about it, rule of thumb implicitly applied on the codebase and already discussed with Charles : complex, advanced or generic typing is ok on internal tools that auto resolve their generic from the parameters for example, but as soon as we have types that are used for entire modules or zones in the application and it is obvious that they will be manipulated by devs adding features or maintaining this zone, we should default on keeping simple typing, even if we lose some advantages of strong typing, a good DX is made of multiple small tradeoffs like that. Here I would strongly advocate for keeping RecordFilter type as it is, it has the right level of complexity and the filter area is already quite complex to work with. 
author_association: CONTRIBUTOR
user: login: Bonapara
created_at: 2025-04-25T13:02:27Z
body: @lucasbordeau We took inspiration from what Notion is doing. Let's see how users react before supporting new field types.
author_association: MEMBER