diff --git a/packages/twenty-front/.storybook/preview.tsx b/packages/twenty-front/.storybook/preview.tsx
index 9f763aa8ed..7f14f6ff7a 100644
--- a/packages/twenty-front/.storybook/preview.tsx
+++ b/packages/twenty-front/.storybook/preview.tsx
@@ -21,15 +21,26 @@ initialize({
return;
}
- if (request.url.startsWith('http://localhost:3000/files/data:image')) {
+ if (request.url.startsWith('http://localhost:3000/files/')) {
return;
}
- const requestBody = await request.json();
+ try {
+ const requestBody = await request.json();
+
+ // eslint-disable-next-line no-console
+ console.warn(`Unhandled ${request.method} request to ${request.url}
+ with payload ${JSON.stringify(requestBody)}\n
+ This request should be mocked with MSW`);
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.error(`Cannot parse msw request body : ${error}`);
+ }
+
// eslint-disable-next-line no-console
- console.warn(`Unhandled ${request.method} request to ${request.url}
- with payload ${JSON.stringify(requestBody)}\n
- This request should be mocked with MSW`);
+ console.warn(
+ `Unhandled ${request.method} request to ${request.url} \n This request should be mocked with MSW`,
+ );
},
quiet: true,
});
diff --git a/packages/twenty-front/src/modules/object-metadata/states/fieldMetadataItemByIdSelector.ts b/packages/twenty-front/src/modules/object-metadata/states/fieldMetadataItemByIdSelector.ts
new file mode 100644
index 0000000000..b960594730
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-metadata/states/fieldMetadataItemByIdSelector.ts
@@ -0,0 +1,38 @@
+import { flattenedFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedFieldMetadataItemsSelector';
+import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
+import { selectorFamily } from 'recoil';
+import { findById, isDefined } from 'twenty-shared/utils';
+
+export const fieldMetadataItemByIdSelector = selectorFamily({
+ key: 'fieldMetadataItemByIdSelector',
+ get:
+ ({ fieldMetadataItemId }: { fieldMetadataItemId: string }) =>
+ ({ get }) => {
+ const objectMetadataItems = get(objectMetadataItemsState);
+ const flattenedFieldMetadataItems = get(
+ flattenedFieldMetadataItemsSelector,
+ );
+
+ const foundObjectMetadataItem = objectMetadataItems.find(
+ (objectMetadataItem) =>
+ objectMetadataItem.fields.some(findById(fieldMetadataItemId)),
+ );
+
+ if (!isDefined(foundObjectMetadataItem)) {
+ return {};
+ }
+
+ const foundFieldMetadataItem = flattenedFieldMetadataItems.find(
+ findById(fieldMetadataItemId),
+ );
+
+ if (!isDefined(foundFieldMetadataItem)) {
+ return {};
+ }
+
+ return {
+ foundFieldMetadataItem,
+ foundObjectMetadataItem,
+ };
+ },
+});
diff --git a/packages/twenty-front/src/modules/object-metadata/states/flattenedFieldMetadataItemsSelector.ts b/packages/twenty-front/src/modules/object-metadata/states/flattenedFieldMetadataItemsSelector.ts
new file mode 100644
index 0000000000..ebc99956d0
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-metadata/states/flattenedFieldMetadataItemsSelector.ts
@@ -0,0 +1,18 @@
+import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
+import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
+import { selector } from 'recoil';
+
+export const flattenedFieldMetadataItemsSelector = selector<
+ FieldMetadataItem[]
+>({
+ key: 'flattenedFieldMetadataItemsSelector',
+ get: ({ get }) => {
+ const objectMetadataItems = get(objectMetadataItemsState);
+
+ const flattenedFieldMetadataItems = objectMetadataItems.flatMap(
+ (objectMetadataItem) => objectMetadataItem.fields,
+ );
+
+ return flattenedFieldMetadataItems;
+ },
+});
diff --git a/packages/twenty-front/src/modules/object-metadata/states/isFieldMetadataItemFilterableAndSortableSelector.ts b/packages/twenty-front/src/modules/object-metadata/states/isFieldMetadataItemFilterableAndSortableSelector.ts
new file mode 100644
index 0000000000..5d5466047d
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-metadata/states/isFieldMetadataItemFilterableAndSortableSelector.ts
@@ -0,0 +1,66 @@
+import { availableFieldMetadataItemsForFilterFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForFilterFamilySelector';
+import { availableFieldMetadataItemsForSortFamilySelector } from '@/object-metadata/states/availableFieldMetadataItemsForSortFamilySelector';
+import { flattenedFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedFieldMetadataItemsSelector';
+import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
+import { selectorFamily } from 'recoil';
+import { findById, isDefined } from 'twenty-shared/utils';
+
+export const isFieldMetadataItemFilterableAndSortableSelector = selectorFamily({
+ key: 'isFieldMetadataItemFilterableAndSortableSelector',
+ get:
+ ({ fieldMetadataItemId }: { fieldMetadataItemId: string }) =>
+ ({ get }) => {
+ const objectMetadataItems = get(objectMetadataItemsState);
+ const flattenedFieldMetadataItems = get(
+ flattenedFieldMetadataItemsSelector,
+ );
+
+ const foundObjectMetadataItem = objectMetadataItems.find(
+ (objectMetadataItem) =>
+ objectMetadataItem.fields.some(findById(fieldMetadataItemId)),
+ );
+
+ if (!isDefined(foundObjectMetadataItem)) {
+ return {
+ isFilterable: false,
+ isSortable: false,
+ };
+ }
+
+ const foundFieldMetadataItem = flattenedFieldMetadataItems.find(
+ findById(fieldMetadataItemId),
+ );
+
+ if (!isDefined(foundFieldMetadataItem)) {
+ return {
+ isFilterable: false,
+ isSortable: false,
+ };
+ }
+
+ const filterableFieldMetadataItems = get(
+ availableFieldMetadataItemsForFilterFamilySelector({
+ objectMetadataItemId: foundObjectMetadataItem.id,
+ }),
+ );
+
+ const sortableFieldMetadataItems = get(
+ availableFieldMetadataItemsForSortFamilySelector({
+ objectMetadataItemId: foundObjectMetadataItem.id,
+ }),
+ );
+
+ const isFilterable = filterableFieldMetadataItems.some(
+ findById(fieldMetadataItemId),
+ );
+
+ const isSortable = sortableFieldMetadataItems.some(
+ findById(fieldMetadataItemId),
+ );
+
+ return {
+ isFilterable,
+ isSortable,
+ };
+ },
+});
diff --git a/packages/twenty-front/src/modules/object-metadata/states/isFieldMetadataItemLabelIdentifierSelector.ts b/packages/twenty-front/src/modules/object-metadata/states/isFieldMetadataItemLabelIdentifierSelector.ts
new file mode 100644
index 0000000000..c55b0ae0b0
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-metadata/states/isFieldMetadataItemLabelIdentifierSelector.ts
@@ -0,0 +1,37 @@
+import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
+import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
+import { selectorFamily } from 'recoil';
+import { findById, isDefined } from 'twenty-shared/utils';
+
+export const isFieldMetadataItemLabelIdentifierSelector = selectorFamily({
+ key: 'isFieldMetadataItemLabelIdentifierSelector',
+ get:
+ ({ fieldMetadataItemId }: { fieldMetadataItemId: string }) =>
+ ({ get }) => {
+ const objectMetadataItems = get(objectMetadataItemsState);
+
+ const foundObjectMetadataItem = objectMetadataItems.find(
+ (objectMetadataItem) =>
+ objectMetadataItem.fields.some(findById(fieldMetadataItemId)),
+ );
+
+ if (!isDefined(foundObjectMetadataItem)) {
+ return false;
+ }
+
+ const foundFieldMetadataItem = foundObjectMetadataItem.fields.find(
+ findById(fieldMetadataItemId),
+ );
+
+ if (!isDefined(foundFieldMetadataItem)) {
+ return false;
+ }
+
+ const fieldIsLabelIdentifier = isLabelIdentifierField({
+ fieldMetadataItem: foundFieldMetadataItem,
+ objectMetadataItem: foundObjectMetadataItem,
+ });
+
+ return fieldIsLabelIdentifier;
+ },
+});
diff --git a/packages/twenty-front/src/modules/object-metadata/states/labelIdentifierFieldMetadataItemSelector.ts b/packages/twenty-front/src/modules/object-metadata/states/labelIdentifierFieldMetadataItemSelector.ts
new file mode 100644
index 0000000000..30840bf25a
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-metadata/states/labelIdentifierFieldMetadataItemSelector.ts
@@ -0,0 +1,31 @@
+import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
+import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
+import { selectorFamily } from 'recoil';
+import { isDefined } from 'twenty-shared/utils';
+
+export const labelIdentifierFieldMetadataItemSelector = selectorFamily({
+ key: 'labelIdentifierFieldMetadataItemSelector',
+ get:
+ ({ objectMetadataItemId }: { objectMetadataItemId: string }) =>
+ ({ get }) => {
+ const objectMetadataItems = get(objectMetadataItemsState);
+
+ const objectMetadataItem = objectMetadataItems.find(
+ (objectMetadataItem) => objectMetadataItem.id === objectMetadataItemId,
+ );
+
+ if (!isDefined(objectMetadataItem)) {
+ return undefined;
+ }
+
+ const labelIdentifierFieldMetadataItem = objectMetadataItem.fields.find(
+ (fieldMetadataItemToFind) =>
+ isLabelIdentifierField({
+ fieldMetadataItem: fieldMetadataItemToFind,
+ objectMetadataItem: objectMetadataItem,
+ }),
+ );
+
+ return labelIdentifierFieldMetadataItem;
+ },
+});
diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterFieldSelectMenu.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterFieldSelectMenu.tsx
index 9d752c3d14..b0a6aa6f96 100644
--- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterFieldSelectMenu.tsx
+++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterFieldSelectMenu.tsx
@@ -3,7 +3,6 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
-import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
@@ -20,6 +19,7 @@ import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record
import { objectFilterDropdownIsSelectingCompositeFieldComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownIsSelectingCompositeFieldComponentState';
import { objectFilterDropdownSubMenuFieldTypeComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSubMenuFieldTypeComponentState';
import { isCompositeFieldType } from '@/object-record/object-filter-dropdown/utils/isCompositeFieldType';
+import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector';
import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuSectionLabel } from '@/ui/layout/dropdown/components/DropdownMenuSectionLabel';
@@ -49,12 +49,12 @@ export const AdvancedFilterFieldSelectMenu = ({
const { filterableFieldMetadataItems } =
useFilterableFieldMetadataItemsInRecordIndexContext();
- const visibleTableColumns = useRecoilComponentValue(
- visibleTableColumnsComponentSelector,
+ const visibleRecordFields = useRecoilComponentValue(
+ visibleRecordFieldsComponentSelector,
recordIndexId,
);
- const visibleColumnsIds = visibleTableColumns.map(
- (column) => column.fieldMetadataId,
+ const visibleFieldMetadataItemIds = visibleRecordFields.map(
+ (recordField) => recordField.fieldMetadataItemId,
);
const filteredSearchInputFieldMetadataItems =
@@ -64,18 +64,22 @@ export const AdvancedFilterFieldSelectMenu = ({
.includes(objectFilterDropdownSearchInput.toLocaleLowerCase()),
);
- const visibleColumnsFieldMetadataItems = filteredSearchInputFieldMetadataItems
- .sort((a, b) => {
- return visibleColumnsIds.indexOf(a.id) - visibleColumnsIds.indexOf(b.id);
+ const visibleFieldMetadataItems = filteredSearchInputFieldMetadataItems
+ .toSorted((a, b) => {
+ return (
+ visibleFieldMetadataItemIds.indexOf(a.id) -
+ visibleFieldMetadataItemIds.indexOf(b.id)
+ );
})
.filter((fieldMetadataItem) =>
- visibleColumnsIds.includes(fieldMetadataItem.id),
+ visibleFieldMetadataItemIds.includes(fieldMetadataItem.id),
);
const hiddenColumnsFieldMetadataItems = filteredSearchInputFieldMetadataItems
.sort((a, b) => a.label.localeCompare(b.label))
.filter(
- (fieldMetadataItem) => !visibleColumnsIds.includes(fieldMetadataItem.id),
+ (fieldMetadataItem) =>
+ !visibleFieldMetadataItemIds.includes(fieldMetadataItem.id),
);
const { resetSelectedItem } = useSelectableList(
@@ -122,15 +126,15 @@ export const AdvancedFilterFieldSelectMenu = ({
}
};
- const shouldShowVisibleFields = visibleColumnsFieldMetadataItems.length > 0;
+ const shouldShowVisibleFields = visibleFieldMetadataItems.length > 0;
const shouldShowHiddenFields = hiddenColumnsFieldMetadataItems.length > 0;
const shouldShowSeparator =
- visibleColumnsFieldMetadataItems.length > 0 &&
+ visibleFieldMetadataItems.length > 0 &&
hiddenColumnsFieldMetadataItems.length > 0;
const selectableItemIdArray = [
- ...visibleColumnsFieldMetadataItems.map(
+ ...visibleFieldMetadataItems.map(
(fieldMetadataItem) => fieldMetadataItem.id,
),
...hiddenColumnsFieldMetadataItems.map(
@@ -152,7 +156,7 @@ export const AdvancedFilterFieldSelectMenu = ({
<>
- {visibleColumnsFieldMetadataItems.map(
+ {visibleFieldMetadataItems.map(
(visibleFieldMetadataItem, index) => (
}
{shouldShowHiddenFields && (
<>
- {visibleColumnsFieldMetadataItems.length > 0 && (
+ {visibleFieldMetadataItems.length > 0 && (
)}
diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useRecordIndexTableQuery.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useRecordIndexTableQuery.test.tsx
index 9aafc0f98c..d735bd5255 100644
--- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useRecordIndexTableQuery.test.tsx
+++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useRecordIndexTableQuery.test.tsx
@@ -1,9 +1,12 @@
import { renderHook, waitFor } from '@testing-library/react';
import { type ReactNode } from 'react';
+import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper';
import { RecordGroupContext } from '@/object-record/record-group/states/context/RecordGroupContext';
import { useRecordIndexTableQuery } from '@/object-record/record-index/hooks/useRecordIndexTableQuery';
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
+
+import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
import { type MockedResponse } from '@apollo/client/testing';
import gql from 'graphql-tag';
@@ -657,17 +660,29 @@ jest.mock('react-router-dom', () => ({
const Wrapper = ({ children }: { children: ReactNode }) => {
return (
-
-
-
-
- {children}
-
-
-
-
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+
);
};
diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/hooks/useFilterDropdownSelectableFieldMetadataItems.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/hooks/useFilterDropdownSelectableFieldMetadataItems.ts
index 8992beb096..d5d79768de 100644
--- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/hooks/useFilterDropdownSelectableFieldMetadataItems.ts
+++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/hooks/useFilterDropdownSelectableFieldMetadataItems.ts
@@ -1,7 +1,7 @@
import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState';
+import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector';
import { useFilterableFieldMetadataItemsInRecordIndexContext } from '@/object-record/record-filter/hooks/useFilterableFieldMetadataItemsInRecordIndexContext';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
-import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
export const useFilterDropdownSelectableFieldMetadataItems = () => {
@@ -14,13 +14,13 @@ export const useFilterDropdownSelectableFieldMetadataItems = () => {
const { filterableFieldMetadataItems } =
useFilterableFieldMetadataItemsInRecordIndexContext();
- const visibleTableColumns = useRecoilComponentValue(
- visibleTableColumnsComponentSelector,
+ const visibleRecordFields = useRecoilComponentValue(
+ visibleRecordFieldsComponentSelector,
recordIndexId,
);
- const visibleColumnsIds = visibleTableColumns.map(
- (column) => column.fieldMetadataId,
+ const visibleFieldMetadataItemIds = visibleRecordFields.map(
+ (recordField) => recordField.fieldMetadataItemId,
);
const filteredSearchInputFieldMetadataItems =
@@ -34,11 +34,12 @@ export const useFilterDropdownSelectableFieldMetadataItems = () => {
filteredSearchInputFieldMetadataItems
.sort((a, b) => {
return (
- visibleColumnsIds.indexOf(a.id) - visibleColumnsIds.indexOf(b.id)
+ visibleFieldMetadataItemIds.indexOf(a.id) -
+ visibleFieldMetadataItemIds.indexOf(b.id)
);
})
.filter((fieldMetadataItem) =>
- visibleColumnsIds.includes(fieldMetadataItem.id),
+ visibleFieldMetadataItemIds.includes(fieldMetadataItem.id),
);
const selectableHiddenFieldMetadataItems =
@@ -46,7 +47,7 @@ export const useFilterDropdownSelectableFieldMetadataItems = () => {
.sort((a, b) => a.label.localeCompare(b.label))
.filter(
(fieldMetadataItem) =>
- !visibleColumnsIds.includes(fieldMetadataItem.id),
+ !visibleFieldMetadataItemIds.includes(fieldMetadataItem.id),
);
return {
diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/hooks/useObjectOptionsForTable.ts b/packages/twenty-front/src/modules/object-record/object-options-dropdown/hooks/useObjectOptionsForTable.ts
deleted file mode 100644
index 3752d3d94b..0000000000
--- a/packages/twenty-front/src/modules/object-record/object-options-dropdown/hooks/useObjectOptionsForTable.ts
+++ /dev/null
@@ -1,116 +0,0 @@
-import { type OnDragEndResponder } from '@hello-pangea/dnd';
-
-import { useFilterVisibleAndReadableRecordField } from '@/object-record/record-field/hooks/useFilterVisibleAndReadableRecordField';
-import { useReorderVisibleRecordFields } from '@/object-record/record-field/hooks/useReorderVisibleRecordFields';
-import { currentRecordFieldsComponentState } from '@/object-record/record-field/states/currentRecordFieldsComponentState';
-import { useSetTableColumns } from '@/object-record/record-table/hooks/useSetTableColumns';
-import { useTableColumns } from '@/object-record/record-table/hooks/useTableColumns';
-import { hiddenTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/hiddenTableColumnsComponentSelector';
-import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
-import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
-import { useSaveCurrentViewFields } from '@/views/hooks/useSaveCurrentViewFields';
-import { mapRecordFieldToViewField } from '@/views/utils/mapRecordFieldToViewField';
-import { produce } from 'immer';
-import { useRecoilCallback } from 'recoil';
-import { isDefined } from 'twenty-shared/utils';
-import { sortByProperty } from '~/utils/array/sortByProperty';
-
-export const useObjectOptionsForTable = (
- recordTableId: string,
- objectMetadataId: string,
-) => {
- const hiddenTableColumns = useRecoilComponentValue(
- hiddenTableColumnsComponentSelector,
- recordTableId,
- );
-
- const tableColumns = useRecoilComponentValue(
- tableColumnsComponentState,
- recordTableId,
- );
-
- const currentRecordFields = useRecoilComponentValue(
- currentRecordFieldsComponentState,
- recordTableId,
- );
-
- const { filterVisibleAndReadableRecordField } =
- useFilterVisibleAndReadableRecordField();
-
- const visibleRecordFields = currentRecordFields
- .filter(filterVisibleAndReadableRecordField)
- .toSorted(sortByProperty('position'));
-
- const { handleColumnVisibilityChange } = useTableColumns({
- recordTableId,
- });
-
- const { reorderVisibleRecordFields } =
- useReorderVisibleRecordFields(recordTableId);
-
- const { saveViewFields } = useSaveCurrentViewFields();
-
- const { setTableColumns } = useSetTableColumns();
-
- const handleReorderColumns: OnDragEndResponder = useRecoilCallback(
- () => async (result) => {
- if (
- !result.destination ||
- result.destination.index === 1 ||
- result.source.index === 1
- ) {
- return;
- }
-
- const updatedRecordField = reorderVisibleRecordFields({
- fromIndex: result.source.index - 1,
- toIndex: result.destination.index - 1,
- });
-
- saveViewFields([mapRecordFieldToViewField(updatedRecordField)]);
-
- // TODO: remove this after refactor
- const modifiedVisibleTableColumns = produce(
- tableColumns,
- (draftTableColumns) => {
- const indexToModify = draftTableColumns.findIndex(
- (tableColumnToFind) =>
- tableColumnToFind.fieldMetadataId ===
- updatedRecordField.fieldMetadataItemId,
- );
-
- if (isDefined(draftTableColumns[indexToModify])) {
- draftTableColumns[indexToModify].position =
- updatedRecordField.position;
- } else {
- throw new Error(
- `Undefined draftTableColumns this should not happen`,
- );
- }
- },
- );
-
- // TODO: remove after refactor
- setTableColumns(
- modifiedVisibleTableColumns,
- recordTableId,
- objectMetadataId,
- );
- },
- [
- reorderVisibleRecordFields,
- saveViewFields,
- objectMetadataId,
- recordTableId,
- setTableColumns,
- tableColumns,
- ],
- );
-
- return {
- handleReorderColumns,
- handleColumnVisibilityChange,
- visibleTableColumns: visibleRecordFields,
- hiddenTableColumns,
- };
-};
diff --git a/packages/twenty-front/src/modules/object-record/object-options-dropdown/hooks/useProcessOptionDropdownDragEnd.ts b/packages/twenty-front/src/modules/object-record/object-options-dropdown/hooks/useProcessOptionDropdownDragEnd.ts
new file mode 100644
index 0000000000..23e85e0ec7
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/object-options-dropdown/hooks/useProcessOptionDropdownDragEnd.ts
@@ -0,0 +1,38 @@
+import { type OnDragEndResponder } from '@hello-pangea/dnd';
+
+import { useReorderVisibleRecordFields } from '@/object-record/record-field/hooks/useReorderVisibleRecordFields';
+
+import { useSaveCurrentViewFields } from '@/views/hooks/useSaveCurrentViewFields';
+import { mapRecordFieldToViewField } from '@/views/utils/mapRecordFieldToViewField';
+import { useRecoilCallback } from 'recoil';
+
+export const useProcessOptionDropdownDragEnd = (recordTableId: string) => {
+ const { reorderVisibleRecordFields } =
+ useReorderVisibleRecordFields(recordTableId);
+
+ const { saveViewFields } = useSaveCurrentViewFields();
+
+ const processOptionDropdownDragEnd: OnDragEndResponder = useRecoilCallback(
+ () => async (result) => {
+ if (
+ !result.destination ||
+ result.destination.index === 1 ||
+ result.source.index === 1
+ ) {
+ return;
+ }
+
+ const updatedRecordField = reorderVisibleRecordFields({
+ fromIndex: result.source.index - 1,
+ toIndex: result.destination.index - 1,
+ });
+
+ saveViewFields([mapRecordFieldToViewField(updatedRecordField)]);
+ },
+ [reorderVisibleRecordFields, saveViewFields],
+ );
+
+ return {
+ processOptionDropdownDragEnd,
+ };
+};
diff --git a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
index 2ff649c45a..910cde6476 100644
--- a/packages/twenty-front/src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
+++ b/packages/twenty-front/src/modules/object-record/object-sort-dropdown/components/ObjectSortDropdownButton.tsx
@@ -7,14 +7,13 @@ import { useResetSortDropdown } from '@/object-record/object-sort-dropdown/hooks
import { isRecordSortDirectionDropdownMenuUnfoldedComponentState } from '@/object-record/object-sort-dropdown/states/isRecordSortDirectionDropdownMenuUnfoldedComponentState';
import { objectSortDropdownSearchInputComponentState } from '@/object-record/object-sort-dropdown/states/objectSortDropdownSearchInputComponentState';
import { selectedRecordSortDirectionComponentState } from '@/object-record/object-sort-dropdown/states/selectedRecordSortDirectionComponentState';
+import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useUpsertRecordSort } from '@/object-record/record-sort/hooks/useUpsertRecordSort';
import {
RECORD_SORT_DIRECTIONS,
type RecordSortDirection,
} from '@/object-record/record-sort/types/RecordSortDirection';
-import { hiddenTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/hiddenTableColumnsComponentSelector';
-import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
@@ -35,6 +34,7 @@ import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/ho
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import { Trans, useLingui } from '@lingui/react/macro';
import { useRecoilValue } from 'recoil';
+import { findByProperty } from 'twenty-shared/utils';
import { IconX, useIcons } from 'twenty-ui/display';
import { MenuItem } from 'twenty-ui/navigation';
import { v4 } from 'uuid';
@@ -63,19 +63,13 @@ export const ObjectSortDropdownButton = () => {
const { getIcon } = useIcons();
- const visibleTableColumns = useRecoilComponentValue(
- visibleTableColumnsComponentSelector,
+ const visibleRecordFields = useRecoilComponentValue(
+ visibleRecordFieldsComponentSelector,
recordIndexId,
);
- const visibleColumnsFieldMetadataIds = visibleTableColumns.map(
- (column) => column.fieldMetadataId,
- );
- const hiddenTableColumns = useRecoilComponentValue(
- hiddenTableColumnsComponentSelector,
- recordIndexId,
- );
- const hiddenColumnFieldMetadataIds = hiddenTableColumns.map(
- (column) => column.fieldMetadataId,
+
+ const visibleFieldMetadataItemIds = visibleRecordFields.map(
+ (recordField) => recordField.fieldMetadataItemId,
);
const filteredSearchInputFieldMetadataItems =
@@ -88,24 +82,28 @@ export const ObjectSortDropdownButton = () => {
const visibleFieldMetadataItems = filteredSearchInputFieldMetadataItems
.sort((fieldMetadataItemA, fieldMetadataItemB) => {
return (
- visibleColumnsFieldMetadataIds.indexOf(fieldMetadataItemA.id) -
- visibleColumnsFieldMetadataIds.indexOf(fieldMetadataItemB.id)
+ visibleFieldMetadataItemIds.indexOf(fieldMetadataItemA.id) -
+ visibleFieldMetadataItemIds.indexOf(fieldMetadataItemB.id)
);
})
.filter((fieldMetadataItem) =>
- visibleColumnsFieldMetadataIds.includes(fieldMetadataItem.id),
+ visibleFieldMetadataItemIds.includes(fieldMetadataItem.id),
);
- const hiddenFieldMetadataItems = filteredSearchInputFieldMetadataItems
+ const hiddenFieldMetadataItemsSorted = filteredSearchInputFieldMetadataItems
.sort((fieldMetadataItemA, fieldMetadataItemB) =>
fieldMetadataItemA.label.localeCompare(fieldMetadataItemB.label),
)
- .filter((fieldMetadataItem) =>
- hiddenColumnFieldMetadataIds.includes(fieldMetadataItem.id),
+ .filter(
+ (fieldMetadataItem) =>
+ !visibleRecordFields.some(
+ findByProperty('fieldMetadataItemId', fieldMetadataItem.id),
+ ),
);
const shouldShowSeparator =
- visibleFieldMetadataItems.length > 0 && hiddenFieldMetadataItems.length > 0;
+ visibleFieldMetadataItems.length > 0 &&
+ hiddenFieldMetadataItemsSorted.length > 0;
const handleDropdownButtonClose = () => {
resetRecordSortDropdownSearchInput();
@@ -152,7 +150,7 @@ export const ObjectSortDropdownButton = () => {
const selectableItemIdArray = [
...visibleFieldMetadataItems.map((item) => item.id),
- ...hiddenFieldMetadataItems.map((item) => item.id),
+ ...hiddenFieldMetadataItemsSorted.map((item) => item.id),
];
const selectedItemId = useRecoilComponentValue(
@@ -165,7 +163,7 @@ export const ObjectSortDropdownButton = () => {
OBJECT_SORT_DROPDOWN_ID,
);
- const shouldShowHiddenFields = hiddenFieldMetadataItems.length > 0;
+ const shouldShowHiddenFields = hiddenFieldMetadataItemsSorted.length > 0;
const shouldShowVisibleFields = visibleFieldMetadataItems.length > 0;
return (
@@ -257,7 +255,7 @@ export const ObjectSortDropdownButton = () => {
<>
- {hiddenFieldMetadataItems.map(
+ {hiddenFieldMetadataItemsSorted.map(
(hiddenFieldMetadataItem, index) => (
{
+ const currentRecordFields = useRecoilComponentValue(
+ currentRecordFieldsComponentState,
+ recordFieldComponentInstanceId,
+ );
+
+ const { updateRecordField } = useUpdateRecordField(
+ recordFieldComponentInstanceId,
+ );
+ const { upsertRecordField } = useUpsertRecordField(
+ recordFieldComponentInstanceId,
+ );
+
+ const { saveViewFields } = useSaveCurrentViewFields();
+
+ const changeRecordFieldVisibility = ({
+ fieldMetadataId,
+ isVisible,
+ }: {
+ fieldMetadataId: string;
+ isVisible: boolean;
+ }) => {
+ const lastPosition =
+ currentRecordFields.toSorted(sortByProperty('position', 'desc'))?.[0]
+ ?.position ?? 0;
+
+ const shouldShowFieldMetadataItem = isVisible === true;
+ const correspondingRecordField = currentRecordFields.find(
+ (recordFieldToFind) =>
+ recordFieldToFind.fieldMetadataItemId === fieldMetadataId,
+ );
+
+ const noExistingRecordField = !isDefined(correspondingRecordField);
+
+ if (noExistingRecordField) {
+ const recordFieldToUpsert: RecordField = {
+ id: v4(),
+ fieldMetadataItemId: fieldMetadataId,
+ size: 100,
+ isVisible: shouldShowFieldMetadataItem,
+ position: lastPosition + 1,
+ };
+
+ upsertRecordField(recordFieldToUpsert);
+
+ saveViewFields([mapRecordFieldToViewField(recordFieldToUpsert)]);
+ } else {
+ updateRecordField(fieldMetadataId, {
+ isVisible: shouldShowFieldMetadataItem,
+ });
+
+ const updatedRecordField: RecordField = {
+ ...correspondingRecordField,
+ isVisible: shouldShowFieldMetadataItem,
+ };
+
+ saveViewFields([mapRecordFieldToViewField(updatedRecordField)]);
+ }
+ };
+
+ return {
+ changeRecordFieldVisibility,
+ };
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/useCreateTableColumn.ts b/packages/twenty-front/src/modules/object-record/record-field/hooks/useCreateTableColumn.ts
deleted file mode 100644
index 11536ae65f..0000000000
--- a/packages/twenty-front/src/modules/object-record/record-field/hooks/useCreateTableColumn.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-import { availableTableColumnsComponentState } from '@/object-record/record-table/states/availableTableColumnsComponentState';
-import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
-import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
-import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
-import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
-import { produce } from 'immer';
-import { useRecoilCallback } from 'recoil';
-import { isDefined } from 'twenty-shared/utils';
-
-// TODO: remove after refactor
-export const useCreateTableColumn = (recordTableId?: string) => {
- const tableColumnsCallbackState = useRecoilComponentCallbackState(
- tableColumnsComponentState,
- recordTableId,
- );
-
- const availableTableColumnsCallbackState = useRecoilComponentCallbackState(
- availableTableColumnsComponentState,
- recordTableId,
- );
-
- const createTableColumn = useRecoilCallback(
- ({ set, snapshot }) =>
- (
- fieldMetadataItemId: string,
- tableColumnToCreateInfo: Pick<
- ColumnDefinition,
- 'isVisible' | 'position' | 'size'
- >,
- ) => {
- const tableColumns = getSnapshotValue(
- snapshot,
- tableColumnsCallbackState,
- );
-
- const availableTableColumns = getSnapshotValue(
- snapshot,
- availableTableColumnsCallbackState,
- );
-
- const modifiedTableColumns = produce(
- tableColumns,
- (draftTableColumns) => {
- const indexToModify = draftTableColumns.findIndex(
- (tableColumnToFind) =>
- tableColumnToFind.fieldMetadataId === fieldMetadataItemId,
- );
-
- const correspondingAvailableTableColumn =
- availableTableColumns.find(
- (tableColumnToFind) =>
- tableColumnToFind.fieldMetadataId === fieldMetadataItemId,
- );
-
- if (
- indexToModify === -1 &&
- isDefined(correspondingAvailableTableColumn)
- ) {
- draftTableColumns.push({
- ...correspondingAvailableTableColumn,
- fieldMetadataId: fieldMetadataItemId,
- position: tableColumnToCreateInfo.position,
- isVisible: tableColumnToCreateInfo.isVisible,
- });
- }
- },
- );
-
- set(tableColumnsCallbackState, modifiedTableColumns);
- },
- [tableColumnsCallbackState, availableTableColumnsCallbackState],
- );
-
- return {
- createTableColumn,
- };
-};
diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/useMoveRecordField.ts b/packages/twenty-front/src/modules/object-record/record-field/hooks/useMoveRecordField.ts
index bda62ea713..a6b37dd52a 100644
--- a/packages/twenty-front/src/modules/object-record/record-field/hooks/useMoveRecordField.ts
+++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/useMoveRecordField.ts
@@ -1,5 +1,4 @@
import { useUpdateRecordField } from '@/object-record/record-field/hooks/useUpdateRecordField';
-import { useUpdateTableColumn } from '@/object-record/record-field/hooks/useUpdateTableColumn';
import { currentRecordFieldsComponentState } from '@/object-record/record-field/states/currentRecordFieldsComponentState';
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
import { useSaveCurrentViewFields } from '@/views/hooks/useSaveCurrentViewFields';
@@ -17,8 +16,8 @@ export const useMoveRecordField = (recordTableId?: string) => {
const { updateRecordField } = useUpdateRecordField(recordTableId);
- const { updateTableColumn } = useUpdateTableColumn(recordTableId);
-
+ // TODO: fix this we want to move left and right of VISIBLE record fields,
+ // because otherwise it will just do nothing while moving left and right of non visible record fields
const moveRecordField = useRecoilCallback(
({ snapshot }) =>
({
@@ -84,22 +83,9 @@ export const useMoveRecordField = (recordTableId?: string) => {
position: currentRecordFieldNewPosition,
}),
]);
-
- updateTableColumn(targetRecordField.fieldMetadataItemId, {
- position: targetRecordFieldNewPosition,
- });
-
- updateTableColumn(currentRecordField.fieldMetadataItemId, {
- position: currentRecordFieldNewPosition,
- });
}
},
- [
- currentRecordFieldsCallbackState,
- updateTableColumn,
- saveViewFields,
- updateRecordField,
- ],
+ [currentRecordFieldsCallbackState, saveViewFields, updateRecordField],
);
return { moveRecordField };
diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/useReorderVisibleRecordFields.ts b/packages/twenty-front/src/modules/object-record/record-field/hooks/useReorderVisibleRecordFields.ts
index 5fb06c2451..7e5bb42da1 100644
--- a/packages/twenty-front/src/modules/object-record/record-field/hooks/useReorderVisibleRecordFields.ts
+++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/useReorderVisibleRecordFields.ts
@@ -1,6 +1,6 @@
import { useUpdateRecordField } from '@/object-record/record-field/hooks/useUpdateRecordField';
import { currentRecordFieldsComponentState } from '@/object-record/record-field/states/currentRecordFieldsComponentState';
-import { visibleAndReadableRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleAndReadableRecordFieldsComponentSelector';
+import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector';
import { type RecordField } from '@/object-record/record-field/types/RecordField';
import { computeNewPositionOfRecordWithPosition } from '@/object-record/utils/computeNewPositionOfRecordWithPosition';
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
@@ -8,7 +8,7 @@ import { useRecoilCallback } from 'recoil';
export const useReorderVisibleRecordFields = (recordTableId: string) => {
const visibleRecordFieldsCallbackState = useRecoilComponentCallbackState(
- visibleAndReadableRecordFieldsComponentSelector,
+ visibleRecordFieldsComponentSelector,
recordTableId,
);
diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/useUpdateRecordField.ts b/packages/twenty-front/src/modules/object-record/record-field/hooks/useUpdateRecordField.ts
index 090d70dd50..3a805d81ee 100644
--- a/packages/twenty-front/src/modules/object-record/record-field/hooks/useUpdateRecordField.ts
+++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/useUpdateRecordField.ts
@@ -3,11 +3,14 @@ import { type RecordField } from '@/object-record/record-field/types/RecordField
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { useRecoilCallback } from 'recoil';
+import { isDefined } from 'twenty-shared/utils';
-export const useUpdateRecordField = (recordTableId?: string) => {
+export const useUpdateRecordField = (
+ recordFieldComponentInstanceId?: string,
+) => {
const currentRecordFieldsCallbackState = useRecoilComponentCallbackState(
currentRecordFieldsComponentState,
- recordTableId,
+ recordFieldComponentInstanceId,
);
const updateRecordField = useRecoilCallback(
@@ -23,12 +26,12 @@ export const useUpdateRecordField = (recordTableId?: string) => {
currentRecordFieldsCallbackState,
);
- const foundRecordFieldInCurrentRecordFields = currentRecordFields.some(
+ const foundRecordFieldInCurrentRecordFields = currentRecordFields.find(
(existingRecordField) =>
existingRecordField.fieldMetadataItemId === fieldMetadataItemId,
);
- if (!foundRecordFieldInCurrentRecordFields) {
+ if (!isDefined(foundRecordFieldInCurrentRecordFields)) {
throw new Error(
`Cannot find record field to update with field metadata item id : ${fieldMetadataItemId}`,
);
@@ -48,6 +51,11 @@ export const useUpdateRecordField = (recordTableId?: string) => {
return newCurrentRecordFields;
});
+
+ return {
+ ...foundRecordFieldInCurrentRecordFields,
+ ...partialRecordField,
+ } satisfies RecordField as RecordField;
}
},
[currentRecordFieldsCallbackState],
diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/useUpdateTableColumn.ts b/packages/twenty-front/src/modules/object-record/record-field/hooks/useUpdateTableColumn.ts
deleted file mode 100644
index 1bdfdf2c5f..0000000000
--- a/packages/twenty-front/src/modules/object-record/record-field/hooks/useUpdateTableColumn.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
-import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
-import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
-import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
-import { produce } from 'immer';
-import { useRecoilCallback } from 'recoil';
-import { isDefined } from 'twenty-shared/utils';
-
-export const useUpdateTableColumn = (recordTableId?: string) => {
- const tableColumnsCallbackState = useRecoilComponentCallbackState(
- tableColumnsComponentState,
- recordTableId,
- );
-
- const updateTableColumn = useRecoilCallback(
- ({ set, snapshot }) =>
- (
- fieldMetadataItemId: string,
- partiableTableColumn: Partial<
- Pick, 'isVisible' | 'position'>
- >,
- ) => {
- const tableColumns = getSnapshotValue(
- snapshot,
- tableColumnsCallbackState,
- );
-
- const modifiedTableColumns = produce(
- tableColumns,
- (draftTableColumns) => {
- const indexToModify = draftTableColumns.findIndex(
- (tableColumnToFind) =>
- tableColumnToFind.fieldMetadataId === fieldMetadataItemId,
- );
-
- if (isDefined(partiableTableColumn.position)) {
- draftTableColumns[indexToModify].position =
- partiableTableColumn.position;
- }
-
- if (isDefined(partiableTableColumn.isVisible)) {
- draftTableColumns[indexToModify].isVisible =
- partiableTableColumn.isVisible;
- }
- },
- );
-
- set(tableColumnsCallbackState, modifiedTableColumns);
- },
- [tableColumnsCallbackState],
- );
-
- return {
- updateTableColumn,
- };
-};
diff --git a/packages/twenty-front/src/modules/object-record/record-field/states/visibleAndReadableRecordFieldsComponentSelector.ts b/packages/twenty-front/src/modules/object-record/record-field/states/visibleAndReadableRecordFieldsComponentSelector.ts
deleted file mode 100644
index dece01cfa6..0000000000
--- a/packages/twenty-front/src/modules/object-record/record-field/states/visibleAndReadableRecordFieldsComponentSelector.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { flattenedReadableFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedReadableFieldMetadataItemIdsSelector';
-import { RecordFieldsComponentInstanceContext } from '@/object-record/record-field/states/context/RecordFieldsComponentInstanceContext';
-import { currentRecordFieldsComponentState } from '@/object-record/record-field/states/currentRecordFieldsComponentState';
-import { createComponentSelector } from '@/ui/utilities/state/component-state/utils/createComponentSelector';
-import { sortByProperty } from '~/utils/array/sortByProperty';
-
-export const visibleAndReadableRecordFieldsComponentSelector =
- createComponentSelector({
- key: 'visibleAndReadableRecordFieldsComponentSelector',
- componentInstanceContext: RecordFieldsComponentInstanceContext,
- get:
- ({ instanceId }) =>
- ({ get }) => {
- const currentRecordFields = get(
- currentRecordFieldsComponentState.atomFamily({
- instanceId,
- }),
- );
-
- const readableFieldMetadataItems = get(
- flattenedReadableFieldMetadataItemsSelector,
- );
-
- const filteredVisibleAndReadableRecordFields =
- currentRecordFields.filter(
- (recordFieldToFilter) =>
- recordFieldToFilter.isVisible === true &&
- readableFieldMetadataItems.some(
- (fieldMetadataItemToFilter) =>
- fieldMetadataItemToFilter.id ===
- recordFieldToFilter.fieldMetadataItemId &&
- fieldMetadataItemToFilter.isActive === true &&
- fieldMetadataItemToFilter.isSystem !== true,
- ),
- );
-
- return filteredVisibleAndReadableRecordFields.toSorted(
- sortByProperty('position'),
- );
- },
- });
diff --git a/packages/twenty-front/src/modules/object-record/record-field/states/visibleRecordFieldsComponentSelector.ts b/packages/twenty-front/src/modules/object-record/record-field/states/visibleRecordFieldsComponentSelector.ts
new file mode 100644
index 0000000000..3417f3a352
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-field/states/visibleRecordFieldsComponentSelector.ts
@@ -0,0 +1,39 @@
+import { flattenedReadableFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedReadableFieldMetadataItemIdsSelector';
+import { RecordFieldsComponentInstanceContext } from '@/object-record/record-field/states/context/RecordFieldsComponentInstanceContext';
+import { currentRecordFieldsComponentState } from '@/object-record/record-field/states/currentRecordFieldsComponentState';
+import { createComponentSelector } from '@/ui/utilities/state/component-state/utils/createComponentSelector';
+import { sortByProperty } from '~/utils/array/sortByProperty';
+
+export const visibleRecordFieldsComponentSelector = createComponentSelector({
+ key: 'visibleRecordFieldsComponentSelector',
+ componentInstanceContext: RecordFieldsComponentInstanceContext,
+ get:
+ ({ instanceId }) =>
+ ({ get }) => {
+ const currentRecordFields = get(
+ currentRecordFieldsComponentState.atomFamily({
+ instanceId,
+ }),
+ );
+
+ const readableFieldMetadataItems = get(
+ flattenedReadableFieldMetadataItemsSelector,
+ );
+
+ const filteredVisibleAndReadableRecordFields = currentRecordFields.filter(
+ (recordFieldToFilter) =>
+ recordFieldToFilter.isVisible === true &&
+ readableFieldMetadataItems.some(
+ (fieldMetadataItemToFilter) =>
+ fieldMetadataItemToFilter.id ===
+ recordFieldToFilter.fieldMetadataItemId &&
+ fieldMetadataItemToFilter.isActive === true &&
+ fieldMetadataItemToFilter.isSystem !== true,
+ ),
+ );
+
+ return filteredVisibleAndReadableRecordFields.toSorted(
+ sortByProperty('position'),
+ );
+ },
+});
diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx
index e8522d2b50..3e1e0f0175 100644
--- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexTableContainerEffect.tsx
@@ -3,7 +3,6 @@ import { useEffect } from 'react';
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
-import { useSetAvailableTableColumns } from '@/object-record/record-table/hooks/useSetAvailableTableColumns';
import { viewFieldAggregateOperationState } from '@/object-record/record-table/record-table-footer/states/viewFieldAggregateOperationState';
import { convertAggregateOperationToExtendedAggregateOperation } from '@/object-record/utils/convertAggregateOperationToExtendedAggregateOperation';
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
@@ -12,10 +11,7 @@ import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
export const RecordIndexTableContainerEffect = () => {
- const { recordIndexId, objectNameSingular } = useRecordIndexContextOrThrow();
-
- const { setAvailableTableColumns } =
- useSetAvailableTableColumns(recordIndexId);
+ const { objectNameSingular } = useRecordIndexContextOrThrow();
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
@@ -24,10 +20,6 @@ export const RecordIndexTableContainerEffect = () => {
const { columnDefinitions } =
useColumnDefinitionsFromFieldMetadata(objectMetadataItem);
- useEffect(() => {
- setAvailableTableColumns(columnDefinitions);
- }, [columnDefinitions, setAvailableTableColumns]);
-
const { currentView } = useGetCurrentViewOnly();
const setViewFieldAggregateOperation = useRecoilCallback(
diff --git a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useRecordIndexLazyFetchRecords.test.ts b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useRecordIndexLazyFetchRecords.test.ts
index 46c46dddda..52643bf9dd 100644
--- a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useRecordIndexLazyFetchRecords.test.ts
+++ b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/__tests__/useRecordIndexLazyFetchRecords.test.ts
@@ -6,10 +6,8 @@ import {
useRecordIndexLazyFetchRecords,
} from '../useRecordIndexLazyFetchRecords';
+import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords';
-import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard';
-import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
-import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
import { ViewType } from '@/views/types/ViewType';
import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper';
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
@@ -131,179 +129,39 @@ describe('useRecordData', () => {
it('should call the callback function with fetched data', async () => {
const callback = jest.fn();
mockFetchAllRecords.mockReturnValue([mockPerson]);
+
const { result } = renderHook(
- () =>
- useRecordIndexLazyFetchRecords({
+ () => {
+ const { columnDefinitions } =
+ useColumnDefinitionsFromFieldMetadata(objectMetadataItem);
+
+ const lazyFetchResult = useRecordIndexLazyFetchRecords({
recordIndexId,
objectMetadataItem,
callback,
pageSize: 30,
delayMs: 0,
- }),
+ });
+
+ return {
+ columnDefinitions,
+ lazyFetchResult,
+ };
+ },
{ wrapper: Wrapper },
);
await act(async () => {
- result.current.getTableData();
- });
-
- await waitFor(() => {
- expect(callback).toHaveBeenCalledWith([mockPerson], []);
- });
- });
-
- it('should call the callback function with kanban field included as column if view type is kanban', async () => {
- const callback = jest.fn();
-
- mockFetchAllRecords.mockReturnValue([mockPerson]);
-
- const { result } = renderHook(
- () => {
- const [recordGroupFieldMetadata, setRecordGroupFieldMetadata] =
- useRecoilComponentState(
- recordGroupFieldMetadataComponentState,
- recordIndexId,
- );
-
- return {
- tableData: useRecordIndexLazyFetchRecords({
- recordIndexId,
- objectMetadataItem,
- callback,
- pageSize: 30,
- maximumRequests: 100,
- delayMs: 0,
- viewType: ViewType.Kanban,
- }),
- kanbanFieldName: recordGroupFieldMetadata?.name,
- setRecordGroupFieldMetadata,
- kanbanData: useObjectOptionsForBoard({
- objectNameSingular: objectMetadataItem.nameSingular,
- recordBoardId: recordIndexId,
- viewBarId: recordIndexId,
- }),
- };
- },
- {
- wrapper: Wrapper,
- },
- );
-
- const personObjectMetadataItem = generatedMockObjectMetadataItems.find(
- (item) => item.nameSingular === 'person',
- );
-
- const updatedAtFieldMetadataItem = personObjectMetadataItem?.fields.find(
- (field) => field.name === 'updatedAt',
- );
-
- await act(async () => {
- result.current.setRecordGroupFieldMetadata(updatedAtFieldMetadataItem);
- });
-
- await act(async () => {
- result.current.tableData.getTableData();
+ result.current.lazyFetchResult.getTableData();
});
await waitFor(() => {
expect(callback).toHaveBeenCalledWith(
[mockPerson],
- [
- {
- defaultValue: 'now',
- editButtonIcon: undefined,
- fieldMetadataId: updatedAtFieldMetadataItem?.id,
- iconName: 'IconCalendarClock',
- isFilterable: true,
- isLabelIdentifier: false,
- isSortable: true,
- isVisible: false,
- label: 'Last update',
- labelWidth: undefined,
- metadata: {
- fieldName: 'updatedAt',
- isCustom: false,
- isNullable: false,
- isUIReadOnly: false,
- objectMetadataNameSingular: 'person',
- options: null,
- placeHolder: 'Last update',
- relationFieldMetadataId: undefined,
- relationObjectMetadataId: '',
- relationObjectMetadataNamePlural: '',
- relationObjectMetadataNameSingular: '',
- relationType: undefined,
- morphRelations: [],
- targetFieldMetadataName: '',
- settings: {
- displayFormat: 'RELATIVE',
- },
- },
- position: 9,
- showLabel: undefined,
- size: 100,
- type: 'DATE_TIME',
- },
- ],
+ result.current.columnDefinitions,
);
});
});
-
- it('should not call the callback function with kanban field included as column if view type is table', async () => {
- const callback = jest.fn();
- mockFetchAllRecords.mockReturnValue([mockPerson]);
- const { result } = renderHook(
- () => {
- const [recordGroupFieldMetadata, setRecordGroupFieldMetadata] =
- useRecoilComponentState(
- recordGroupFieldMetadataComponentState,
- recordIndexId,
- );
-
- return {
- tableData: useRecordIndexLazyFetchRecords({
- recordIndexId,
- objectMetadataItem,
- callback,
- pageSize: 30,
- maximumRequests: 100,
- delayMs: 0,
- viewType: ViewType.Table,
- }),
- objectMetadataItem,
- kanbanFieldName: recordGroupFieldMetadata?.name,
- setRecordGroupFieldMetadata,
- kanbanData: useObjectOptionsForBoard({
- objectNameSingular: objectMetadataItem.nameSingular,
- recordBoardId: recordIndexId,
- viewBarId: recordIndexId,
- }),
- };
- },
- {
- wrapper: Wrapper,
- },
- );
-
- await act(async () => {
- const fieldMetadataItem =
- result.current.objectMetadataItem?.fields.find(
- (fieldMetadata) =>
- fieldMetadata.id ===
- result.current.kanbanData.hiddenBoardFields[0].fieldMetadataId,
- );
-
- result.current.setRecordGroupFieldMetadata(fieldMetadataItem);
- });
-
- await act(async () => {
- result.current.tableData.getTableData();
- });
-
- await waitFor(() => {
- expect(callback).toHaveBeenCalledWith([mockPerson], []);
- });
- });
});
describe('utils', () => {
diff --git a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useRecordIndexLazyFetchRecords.ts b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useRecordIndexLazyFetchRecords.ts
index cc2ee57034..46fc0378ac 100644
--- a/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useRecordIndexLazyFetchRecords.ts
+++ b/packages/twenty-front/src/modules/object-record/record-index/export/hooks/useRecordIndexLazyFetchRecords.ts
@@ -7,6 +7,7 @@ import { contextStoreFilterGroupsComponentState } from '@/context-store/states/c
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { computeContextStoreFilters } from '@/context-store/utils/computeContextStoreFilters';
+import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords';
import { EXPORT_TABLE_DATA_DEFAULT_PAGE_SIZE } from '@/object-record/object-options-dropdown/constants/ExportTableDataDefaultPageSize';
@@ -14,7 +15,6 @@ import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdow
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
import { useFindManyRecordIndexTableParams } from '@/object-record/record-index/hooks/useFindManyRecordIndexTableParams';
-import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { ViewType } from '@/views/types/ViewType';
@@ -61,10 +61,6 @@ export const useRecordIndexLazyFetchRecords = ({
const hiddenKanbanFieldColumn = hiddenBoardFields.find(
(column) => column.metadata.fieldName === recordGroupFieldMetadata?.name,
);
- const columns = useRecoilComponentValue(
- visibleTableColumnsComponentSelector,
- recordIndexId,
- );
const contextStoreTargetedRecordsRule = useRecoilComponentValue(
contextStoreTargetedRecordsRuleComponentState,
@@ -97,8 +93,11 @@ export const useRecordIndexLazyFetchRecords = ({
contextStoreAnyFieldFilterValue,
});
+ const { columnDefinitions } =
+ useColumnDefinitionsFromFieldMetadata(objectMetadataItem);
+
const finalColumns = [
- ...columns,
+ ...columnDefinitions,
...(hiddenKanbanFieldColumn && viewType === ViewType.Kanban
? [hiddenKanbanFieldColumn]
: []),
diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexStates.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexStates.ts
index 184a08df88..1a9349d590 100644
--- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexStates.ts
+++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useLoadRecordIndexStates.ts
@@ -11,12 +11,10 @@ import { recordIndexKanbanAggregateOperationState } from '@/object-record/record
import { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState';
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState';
-import { useSetTableColumns } from '@/object-record/record-table/hooks/useSetTableColumns';
import { viewFieldAggregateOperationState } from '@/object-record/record-table/record-table-footer/states/viewFieldAggregateOperationState';
import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { convertAggregateOperationToExtendedAggregateOperation } from '@/object-record/utils/convertAggregateOperationToExtendedAggregateOperation';
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
-import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import { type View } from '@/views/types/View';
import { type ViewField } from '@/views/types/ViewField';
@@ -45,15 +43,9 @@ export const useLoadRecordIndexStates = () => {
);
const { setRecordGroupsFromViewGroups } = useSetRecordGroups();
- const { setTableColumns } = useSetTableColumns();
-
const onViewFieldsChange = useRecoilCallback(
({ set, snapshot }) =>
- (
- viewFields: ViewField[],
- objectMetadataItem: ObjectMetadataItem,
- recordIndexId: string,
- ) => {
+ (viewFields: ViewField[], objectMetadataItem: ObjectMetadataItem) => {
const activeFieldMetadataItems = objectMetadataItem.fields.filter(
({ isActive, isSystem }) => isActive && !isSystem,
);
@@ -108,12 +100,6 @@ export const useLoadRecordIndexStates = () => {
columnDefinitions,
});
- setTableColumns(
- newFieldDefinitions,
- recordIndexId,
- objectMetadataItem.id,
- );
-
const existingRecordIndexFieldDefinitions = snapshot
.getLoadable(recordIndexFieldDefinitionsState)
.getValue();
@@ -161,17 +147,12 @@ export const useLoadRecordIndexStates = () => {
}
}
},
- [setTableColumns],
+ [],
);
const loadRecordIndexStates = useRecoilCallback(
({ snapshot }) =>
async (view: View, objectMetadataItem: ObjectMetadataItem) => {
- const recordIndexId = getRecordIndexIdFromObjectNamePluralAndViewId(
- objectMetadataItem.namePlural,
- view.id,
- );
-
const filterableFieldMetadataItems = snapshot
.getLoadable(
availableFieldMetadataItemsForFilterFamilySelector({
@@ -180,7 +161,7 @@ export const useLoadRecordIndexStates = () => {
)
.getValue();
- onViewFieldsChange(view.viewFields, objectMetadataItem, recordIndexId);
+ onViewFieldsChange(view.viewFields, objectMetadataItem);
setRecordGroupsFromViewGroups(
view.id,
diff --git a/packages/twenty-front/src/modules/object-record/record-index/hooks/useRecordTableRecordGqlFields.ts b/packages/twenty-front/src/modules/object-record/record-index/hooks/useRecordTableRecordGqlFields.ts
index 5c217ffa75..02ab21e1df 100644
--- a/packages/twenty-front/src/modules/object-record/record-index/hooks/useRecordTableRecordGqlFields.ts
+++ b/packages/twenty-front/src/modules/object-record/record-index/hooks/useRecordTableRecordGqlFields.ts
@@ -3,7 +3,8 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
import { generateDepthOneWithoutRelationsRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneWithoutRelationsRecordGqlFields';
-import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
+import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector';
+import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { FieldMetadataType } from 'twenty-shared/types';
import { computeMorphRelationFieldName, isDefined } from 'twenty-shared/utils';
@@ -13,10 +14,13 @@ export const useRecordTableRecordGqlFields = ({
}: {
objectMetadataItem: ObjectMetadataItem;
}) => {
- const visibleTableColumns = useRecoilComponentValue(
- visibleTableColumnsComponentSelector,
+ const visibleRecordFields = useRecoilComponentValue(
+ visibleRecordFieldsComponentSelector,
);
+ const { fieldMetadataItemByFieldMetadataItemId } =
+ useRecordTableContextOrThrow();
+
const { objectMetadataItem: noteTargetObjectMetadataItem } =
useObjectMetadataItem({
objectNameSingular: CoreObjectNameSingular.NoteTarget,
@@ -33,22 +37,23 @@ export const useRecordTableRecordGqlFields = ({
});
const gqlFieldsList = Object.fromEntries(
- visibleTableColumns.flatMap((column) => {
- const isMorphRelation = column.type === FieldMetadataType.MORPH_RELATION;
+ visibleRecordFields.flatMap((recordField) => {
+ const fieldMetadataItem =
+ fieldMetadataItemByFieldMetadataItemId[recordField.fieldMetadataItemId];
+
+ const isMorphRelation =
+ fieldMetadataItem.type === FieldMetadataType.MORPH_RELATION;
if (!isMorphRelation) {
- return [[column.metadata.fieldName, true]];
+ return [[fieldMetadataItem.name, true]];
}
- const fieldMetadataItem = objectMetadataItem.fields.find(
- (field) => field.id === column.fieldMetadataId,
- );
if (
!isDefined(fieldMetadataItem) ||
!isDefined(fieldMetadataItem.morphRelations)
) {
throw new Error(
- `Field ${column.metadata.fieldName} is missing, please refresh the page. If the problem persists, please contact support.`,
+ `Field ${fieldMetadataItem.name} is missing, please refresh the page. If the problem persists, please contact support.`,
);
}
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableContextProvider.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableContextProvider.tsx
index f90316af60..cfa33ea4cd 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableContextProvider.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableContextProvider.tsx
@@ -3,9 +3,13 @@ import { type ReactNode } from 'react';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { RecordTableContextProvider as RecordTableContextInternalProvider } from '@/object-record/record-table/contexts/RecordTableContext';
+import { labelIdentifierFieldMetadataItemSelector } from '@/object-metadata/states/labelIdentifierFieldMetadataItemSelector';
+import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
-import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
+import { currentRecordFieldsComponentState } from '@/object-record/record-field/states/currentRecordFieldsComponentState';
+import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
+import { useRecoilValue } from 'recoil';
type RecordTableContextProviderProps = {
viewBarId: string;
@@ -28,20 +32,65 @@ export const RecordTableContextProvider = ({
objectMetadataItem.id,
);
- const visibleTableColumns = useRecoilComponentValue(
- visibleTableColumnsComponentSelector,
+ const visibleRecordFields = useRecoilComponentValue(
+ visibleRecordFieldsComponentSelector,
recordTableId,
);
+ const currentRecordFields = useRecoilComponentValue(
+ currentRecordFieldsComponentState,
+ recordTableId,
+ );
+
+ const fieldMetadataItems = objectMetadataItem.fields;
+
+ const fieldMetadataItemByFieldMetadataItemId = Object.fromEntries(
+ fieldMetadataItems.map((fieldMetadataItem) => [
+ fieldMetadataItem.id,
+ fieldMetadataItem,
+ ]),
+ );
+
+ const recordFieldByFieldMetadataItemId = Object.fromEntries(
+ currentRecordFields.map((recordField) => [
+ recordField.fieldMetadataItemId,
+ recordField,
+ ]),
+ );
+
+ const fieldDefinitionByFieldMetadataItemId = Object.fromEntries(
+ fieldMetadataItems.map((fieldMetadataItem) => [
+ fieldMetadataItem.id,
+ formatFieldMetadataItemAsColumnDefinition({
+ field: fieldMetadataItem,
+ objectMetadataItem,
+ position:
+ recordFieldByFieldMetadataItemId[fieldMetadataItem.id]?.position ?? 0,
+ labelWidth:
+ recordFieldByFieldMetadataItemId[fieldMetadataItem.id]?.size ?? 0,
+ }),
+ ]),
+ );
+
+ const labelIdentifierFieldMetadataItem = useRecoilValue(
+ labelIdentifierFieldMetadataItemSelector({
+ objectMetadataItemId: objectMetadataItem.id,
+ }),
+ );
+
return (
{children}
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx
index 946539602d..985a249271 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/RecordTableCell.perf.stories.tsx
@@ -1,6 +1,6 @@
import { type Meta, type StoryObj } from '@storybook/react';
import { useEffect } from 'react';
-import { useRecoilState, useSetRecoilState } from 'recoil';
+import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage';
@@ -13,15 +13,25 @@ import { ChipGeneratorsDecorator } from '~/testing/decorators/ChipGeneratorsDeco
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
+import { labelIdentifierFieldMetadataItemSelector } from '@/object-metadata/states/labelIdentifierFieldMetadataItemSelector';
+import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
+import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
+import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper';
+import { currentRecordFieldsComponentState } from '@/object-record/record-field/states/currentRecordFieldsComponentState';
+import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector';
+import { type RecordField } from '@/object-record/record-field/types/RecordField';
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
+import { mockPerformance } from '@/object-record/record-table/components/__stories__/perf/mock';
import { RecordTableBodyContextProvider } from '@/object-record/record-table/contexts/RecordTableBodyContext';
import { RecordTableContextProvider } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableRowContextProvider } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { RecordTableRowDraggableContextProvider } from '@/object-record/record-table/contexts/RecordTableRowDraggableContext';
import { RecordTableCellFieldContextWrapper } from '@/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextWrapper';
+import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
+import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import { ComponentDecorator } from 'twenty-ui/testing';
+
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
-import { mockPerformance } from './mock';
const RelationFieldValueSetterEffect = () => {
const setEntity = useSetRecoilState(
@@ -32,14 +42,36 @@ const RelationFieldValueSetterEffect = () => {
recordStoreFamilyState(mockPerformance.relationRecordId),
);
+ const setCurrentRecordFields = useSetRecoilComponentState(
+ currentRecordFieldsComponentState,
+ 'recordTableId',
+ );
+
const [, setObjectMetadataItems] = useRecoilState(objectMetadataItemsState);
useEffect(() => {
setEntity(mockPerformance.entityValue);
setRelationEntity(mockPerformance.relationFieldValue);
+ setCurrentRecordFields(
+ mockPerformance.visibleTableColumns.map(
+ (tableColumn) =>
+ ({
+ fieldMetadataItemId: tableColumn.fieldMetadataId,
+ id: tableColumn.fieldMetadataId,
+ isVisible: tableColumn.isVisible,
+ position: tableColumn.position,
+ size: tableColumn.size,
+ }) satisfies RecordField,
+ ),
+ );
setObjectMetadataItems(generatedMockObjectMetadataItems);
- }, [setEntity, setRelationEntity, setObjectMetadataItems]);
+ }, [
+ setEntity,
+ setRelationEntity,
+ setObjectMetadataItems,
+ setCurrentRecordFields,
+ ]);
return null;
};
@@ -50,6 +82,55 @@ const meta: Meta = {
MemoryRouterDecorator,
ChipGeneratorsDecorator,
(Story) => {
+ const currentRecordFields = useRecoilComponentValue(
+ currentRecordFieldsComponentState,
+ 'recordTableId',
+ );
+
+ const visibleRecordFields = useRecoilComponentValue(
+ visibleRecordFieldsComponentSelector,
+ 'recordTableId',
+ );
+
+ const fieldMetadataItems = mockPerformance.objectMetadataItem.fields;
+
+ const fieldMetadataItemByFieldMetadataItemId = Object.fromEntries(
+ fieldMetadataItems.map((fieldMetadataItem) => [
+ fieldMetadataItem.id,
+ // TODO: update performance mocks with new data, and merge with common mocks if possible
+ fieldMetadataItem as unknown as FieldMetadataItem,
+ ]),
+ );
+
+ const recordFieldByFieldMetadataItemId = Object.fromEntries(
+ currentRecordFields.map((recordField) => [
+ recordField.fieldMetadataItemId,
+ recordField,
+ ]),
+ );
+
+ const fieldDefinitionByFieldMetadataItemId = Object.fromEntries(
+ fieldMetadataItems.map((fieldMetadataItem) => [
+ fieldMetadataItem.id,
+ formatFieldMetadataItemAsColumnDefinition({
+ // TODO: update performance mocks with new data, and merge with common mocks if possible
+ field: fieldMetadataItem as any,
+ objectMetadataItem: mockPerformance.objectMetadataItem as any,
+ position:
+ recordFieldByFieldMetadataItemId[fieldMetadataItem.id]
+ ?.position ?? 0,
+ labelWidth:
+ recordFieldByFieldMetadataItemId[fieldMetadataItem.id]?.size ?? 0,
+ }),
+ ]),
+ );
+
+ const labelIdentifierFieldMetadataItem = useRecoilValue(
+ labelIdentifierFieldMetadataItemSelector({
+ objectMetadataItemId: mockPerformance.objectMetadataItem.id,
+ }),
+ );
+
return (
{},
objectNamePlural: 'companies',
objectNameSingular: 'company',
+ // TODO: update performance mocks with new data, and merge with common mocks if possible
objectMetadataItem: mockPerformance.objectMetadataItem as any,
recordIndexId: 'recordIndexId',
}}
>
-
-
- {},
- onMoveFocus: () => {},
- onCloseTableCell: () => {},
- onMoveHoverToCurrentCell: () => {},
- onActionMenuDropdownOpened: () => {},
- onCellMouseEnter: () => {},
- }}
- >
-
+
+
+ {},
+ onMoveFocus: () => {},
+ onCloseTableCell: () => {},
+ onMoveHoverToCurrentCell: () => {},
+ onActionMenuDropdownOpened: () => {},
+ onCellMouseEnter: () => {},
}}
>
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
);
},
@@ -145,7 +240,15 @@ const meta: Meta = {
],
component: RecordTableCellFieldContextWrapper,
argTypes: { value: { control: 'date' } },
- args: {},
+ args: {
+ recordField: {
+ fieldMetadataItemId: mockPerformance.fieldDefinition.fieldMetadataId,
+ id: 'test',
+ isVisible: mockPerformance.fieldDefinition.isVisible,
+ position: mockPerformance.fieldDefinition.position,
+ size: mockPerformance.fieldDefinition.size,
+ },
+ },
parameters: {
chromatic: { disableSnapshot: true },
},
diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/mock.ts b/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/mock.ts
index 81c9716467..da090267fa 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/mock.ts
+++ b/packages/twenty-front/src/modules/object-record/record-table/components/__stories__/perf/mock.ts
@@ -1,4 +1,4 @@
-import { FieldMetadataType } from '~/generated-metadata/graphql';
+import { FieldMetadataType } from 'twenty-shared/types';
export const mockPerformance = {
objectMetadataItem: {
@@ -708,26 +708,26 @@ export const mockPerformance = {
},
},
fieldDefinition: {
- fieldMetadataId: '4e79f0b7-d100-4e89-a07b-315a710b8059',
+ fieldMetadataId: '9058056e-36b3-4a3f-9037-f0bca9744296',
primaryLinkLabel: 'Company',
- label: 'Company',
metadata: {
fieldName: 'company',
placeHolder: 'Company',
relationType: 'TO_ONE_OBJECT',
- relationFieldMetadataId: '01fa2247-7937-4493-b7e2-3d72f05d6d25',
+ relationFieldMetadataId: '7b281010-5f47-4771-b3f5-f4bcd24ed1b5',
relationObjectMetadataNameSingular: 'company',
relationObjectMetadataNamePlural: 'companies',
objectMetadataNameSingular: 'person',
options: null,
},
+ label: 'Company',
iconName: 'IconBuildingSkyscraper',
type: FieldMetadataType.RELATION,
position: 2,
size: 150,
isLabelIdentifier: false,
isVisible: true,
- viewFieldId: '924f4c94-cbcd-4de5-b7a2-ebae2f0b2c3b',
+ viewFieldId: '9a479a97-deaa-4ddb-9d59-96f05875ac09',
isSortable: false,
isFilterable: true,
defaultValue: null,
diff --git a/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableCellContext.ts b/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableCellContext.ts
index 41b72cdbcd..33da8cdbda 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableCellContext.ts
+++ b/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableCellContext.ts
@@ -1,11 +1,10 @@
import { createContext } from 'react';
-import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
-import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
+import { type RecordField } from '@/object-record/record-field/types/RecordField';
import { type TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
export type RecordTableCellContextValue = {
- columnDefinition: ColumnDefinition;
+ recordField: RecordField;
cellPosition: TableCellPosition;
};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableContext.ts b/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableContext.ts
index 11e7f542e2..d5b45b2a4e 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableContext.ts
+++ b/packages/twenty-front/src/modules/object-record/record-table/contexts/RecordTableContext.ts
@@ -1,4 +1,6 @@
+import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
+import { type RecordField } from '@/object-record/record-field/types/RecordField';
import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { type ObjectPermission } from '~/generated/graphql';
@@ -9,8 +11,15 @@ type RecordTableContextValue = {
viewBarId: string;
objectNameSingular: string;
objectMetadataItem: ObjectMetadataItem;
- visibleTableColumns: ColumnDefinition[];
objectPermissions: ObjectPermission;
+ visibleRecordFields: RecordField[];
+ recordFieldByFieldMetadataItemId: Record;
+ labelIdentifierFieldMetadataItem: FieldMetadataItem | undefined;
+ fieldMetadataItemByFieldMetadataItemId: Record;
+ fieldDefinitionByFieldMetadataItemId: Record<
+ string,
+ ColumnDefinition
+ >;
};
export const [RecordTableContextProvider, useRecordTableContextOrThrow] =
diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useHandleColumnsChange.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useHandleColumnsChange.ts
deleted file mode 100644
index c3dc3447e7..0000000000
--- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useHandleColumnsChange.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
-import { useSetTableColumns } from '@/object-record/record-table/hooks/useSetTableColumns';
-import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
-
-// TODO: see how we can better abstract this and set the correct interaction between view and table
-// but for now it allows to have a cleaner API globally.
-// TODO: should be solved with new RecordField abstraction
-export const useHandleColumnsChange = () => {
- const { setTableColumns } = useSetTableColumns();
-
- const handleColumnsChange = async ({
- columns,
- recordTableId,
- objectMetadataId,
- }: {
- columns: ColumnDefinition[];
- recordTableId: string;
- objectMetadataId: string;
- }) => {
- setTableColumns(columns, recordTableId, objectMetadataId);
- };
-
- return {
- handleColumnsChange,
- };
-};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useMoveTableColumn.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useMoveTableColumn.ts
new file mode 100644
index 0000000000..85d18af254
--- /dev/null
+++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/useMoveTableColumn.ts
@@ -0,0 +1,31 @@
+import { useCallback } from 'react';
+
+import { useUnfocusRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useUnfocusRecordTableCell';
+
+import { useMoveRecordField } from '@/object-record/record-field/hooks/useMoveRecordField';
+
+type useRecordTableProps = {
+ recordTableId: string;
+};
+
+export const useMoveTableColumn = ({ recordTableId }: useRecordTableProps) => {
+ const { unfocusRecordTableCell } = useUnfocusRecordTableCell(recordTableId);
+
+ const { moveRecordField } = useMoveRecordField(recordTableId);
+
+ const moveTableColumn = useCallback(
+ async (direction: 'left' | 'right', fieldMetadataItemId: string) => {
+ unfocusRecordTableCell();
+
+ moveRecordField({
+ direction: direction === 'left' ? 'before' : 'after',
+ fieldMetadataItemIdToMove: fieldMetadataItemId,
+ });
+ },
+ [unfocusRecordTableCell, moveRecordField],
+ );
+
+ return {
+ moveTableColumn,
+ };
+};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTableMoveFocusedCell.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTableMoveFocusedCell.ts
index 4fe7ca3546..285103d410 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTableMoveFocusedCell.ts
+++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTableMoveFocusedCell.ts
@@ -3,10 +3,10 @@ import { useRecoilCallback } from 'recoil';
import { type MoveFocusDirection } from '@/object-record/record-table/types/MoveFocusDirection';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
+import { currentRecordFieldsComponentState } from '@/object-record/record-field/states/currentRecordFieldsComponentState';
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
import { useFocusRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useFocusRecordTableCell';
import { recordTableFocusPositionComponentState } from '@/object-record/record-table/states/recordTableFocusPositionComponentState';
-import { numberOfTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/numberOfTableColumnsComponentSelector';
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
export const useRecordTableMoveFocusedCell = (recordTableId?: string) => {
@@ -64,8 +64,8 @@ export const useRecordTableMoveFocusedCell = (recordTableId?: string) => {
[recordIndexAllRecordIdsSelector, focusRecordTableCell, focusPositionState],
);
- const numberOfTableColumnsSelector = useRecoilComponentCallbackState(
- numberOfTableColumnsComponentSelector,
+ const currentRecordFieldsCallbackState = useRecoilComponentCallbackState(
+ currentRecordFieldsComponentState,
recordTableId,
);
@@ -78,23 +78,23 @@ export const useRecordTableMoveFocusedCell = (recordTableId?: string) => {
);
const focusPosition = getSnapshotValue(snapshot, focusPositionState);
- const numberOfTableColumns = getSnapshotValue(
+ const numberOfRecordFields = getSnapshotValue(
snapshot,
- numberOfTableColumnsSelector,
- );
+ currentRecordFieldsCallbackState,
+ ).length;
const currentColumnIndex = focusPosition.column;
const currentRowIndex = focusPosition.row;
const isLastRowAndLastColumn =
- currentColumnIndex === numberOfTableColumns - 1 &&
+ currentColumnIndex === numberOfRecordFields - 1 &&
currentRowIndex === allRecordIds.length - 1;
const isLastColumnButNotLastRow =
- currentColumnIndex === numberOfTableColumns - 1 &&
+ currentColumnIndex === numberOfRecordFields - 1 &&
currentRowIndex !== allRecordIds.length - 1;
- const isNotLastColumn = currentColumnIndex !== numberOfTableColumns - 1;
+ const isNotLastColumn = currentColumnIndex !== numberOfRecordFields - 1;
if (isLastRowAndLastColumn) {
return;
@@ -115,7 +115,7 @@ export const useRecordTableMoveFocusedCell = (recordTableId?: string) => {
[
recordIndexAllRecordIdsSelector,
focusPositionState,
- numberOfTableColumnsSelector,
+ currentRecordFieldsCallbackState,
focusRecordTableCell,
],
);
@@ -125,10 +125,10 @@ export const useRecordTableMoveFocusedCell = (recordTableId?: string) => {
() => {
const focusPosition = getSnapshotValue(snapshot, focusPositionState);
- const numberOfTableColumns = getSnapshotValue(
+ const numberOfRecordFields = getSnapshotValue(
snapshot,
- numberOfTableColumnsSelector,
- );
+ currentRecordFieldsCallbackState,
+ ).length;
const currentColumnIndex = focusPosition.column;
const currentRowIndex = focusPosition.row;
@@ -153,11 +153,15 @@ export const useRecordTableMoveFocusedCell = (recordTableId?: string) => {
} else if (isFirstColumnButNotFirstRow) {
focusRecordTableCell({
row: currentRowIndex - 1,
- column: numberOfTableColumns - 1,
+ column: numberOfRecordFields - 1,
});
}
},
- [numberOfTableColumnsSelector, focusPositionState, focusRecordTableCell],
+ [
+ currentRecordFieldsCallbackState,
+ focusPositionState,
+ focusRecordTableCell,
+ ],
);
const moveFocus = (direction: MoveFocusDirection) => {
diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useSaveColumnsToView.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useSaveColumnsToView.ts
deleted file mode 100644
index b64cfe008d..0000000000
--- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useSaveColumnsToView.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
-import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
-import { useSaveCurrentViewFields } from '@/views/hooks/useSaveCurrentViewFields';
-import { mapColumnDefinitionsToViewFields } from '@/views/utils/mapColumnDefinitionToViewField';
-import { useRecoilCallback } from 'recoil';
-
-export const useSaveColumnsToView = () => {
- const { saveViewFields } = useSaveCurrentViewFields();
-
- const saveColumnsToView = useRecoilCallback(
- () => async (columns) => {
- await saveViewFields(
- mapColumnDefinitionsToViewFields(
- columns as ColumnDefinition[],
- ),
- );
- },
- [saveViewFields],
- );
-
- return { saveColumnsToView };
-};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useSetAvailableTableColumns.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useSetAvailableTableColumns.ts
deleted file mode 100644
index 4509436c74..0000000000
--- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useSetAvailableTableColumns.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
-import { availableTableColumnsComponentState } from '@/object-record/record-table/states/availableTableColumnsComponentState';
-import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
-import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
-import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
-import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
-import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
-import { useRecoilCallback } from 'recoil';
-import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
-
-export const useSetAvailableTableColumns = (
- recordTableIdFromProps?: string,
-) => {
- const recordTableId = useAvailableComponentInstanceIdOrThrow(
- RecordTableComponentInstanceContext,
- recordTableIdFromProps,
- );
-
- const availableTableColumnsState = useRecoilComponentCallbackState(
- availableTableColumnsComponentState,
- recordTableId,
- );
-
- const setAvailableTableColumns = useRecoilCallback(
- ({ snapshot, set }) =>
- (columns: ColumnDefinition[]) => {
- const availableTableColumns = getSnapshotValue(
- snapshot,
- availableTableColumnsState,
- );
-
- if (isDeeplyEqual(availableTableColumns, columns)) {
- return;
- }
- set(availableTableColumnsState, columns);
- },
- [availableTableColumnsState],
- );
-
- return {
- setAvailableTableColumns,
- };
-};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useSetTableColumns.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useSetTableColumns.ts
deleted file mode 100644
index cdd4e64841..0000000000
--- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useSetTableColumns.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
-import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
-import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
-import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
-import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
-import { useRecoilCallback } from 'recoil';
-import { isDefined } from 'twenty-shared/utils';
-import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
-
-export const useSetTableColumns = () => {
- const setTableColumns = useRecoilCallback(
- ({ snapshot, set }) =>
- (
- columns: ColumnDefinition[],
- recordTableId: string,
- objectMetadataId: string,
- ) => {
- const objectMetadataItems = getSnapshotValue(
- snapshot,
- objectMetadataItemsState,
- );
-
- const objectMetadataItem = objectMetadataItems.find(
- (item) => item.id === objectMetadataId,
- );
-
- if (!isDefined(objectMetadataItem)) {
- return;
- }
-
- const tableColumns = getSnapshotValue(
- snapshot,
- tableColumnsComponentState.atomFamily({
- instanceId: recordTableId,
- }),
- );
-
- const columnsToSet = columns.filter((column) =>
- objectMetadataItem.readableFields
- .map((field) => field.name)
- .includes(column.metadata.fieldName),
- );
-
- if (isDeeplyEqual(tableColumns, columnsToSet)) {
- return;
- }
- set(
- tableColumnsComponentState.atomFamily({
- instanceId: recordTableId,
- }),
- columnsToSet,
- );
- },
- [],
- );
-
- return { setTableColumns };
-};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useTableColumns.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useTableColumns.ts
deleted file mode 100644
index c726c6d140..0000000000
--- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useTableColumns.ts
+++ /dev/null
@@ -1,130 +0,0 @@
-import { useCallback } from 'react';
-
-import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
-import { useUnfocusRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useUnfocusRecordTableCell';
-
-import { useCreateTableColumn } from '@/object-record/record-field/hooks/useCreateTableColumn';
-import { useMoveRecordField } from '@/object-record/record-field/hooks/useMoveRecordField';
-import { useUpdateRecordField } from '@/object-record/record-field/hooks/useUpdateRecordField';
-import { useUpdateTableColumn } from '@/object-record/record-field/hooks/useUpdateTableColumn';
-import { useUpsertRecordField } from '@/object-record/record-field/hooks/useUpsertRecordField';
-import { currentRecordFieldsComponentState } from '@/object-record/record-field/states/currentRecordFieldsComponentState';
-import { type RecordField } from '@/object-record/record-field/types/RecordField';
-import { useHandleColumnsChange } from '@/object-record/record-table/hooks/useHandleColumnsChange';
-import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
-import { useSaveCurrentViewFields } from '@/views/hooks/useSaveCurrentViewFields';
-import { mapRecordFieldToViewField } from '@/views/utils/mapRecordFieldToViewField';
-import { isDefined } from 'twenty-shared/utils';
-import { v4 } from 'uuid';
-import { sortByProperty } from '~/utils/array/sortByProperty';
-import { type ColumnDefinition } from '../types/ColumnDefinition';
-
-type useRecordTableProps = {
- recordTableId: string;
-};
-
-export const useTableColumns = ({ recordTableId }: useRecordTableProps) => {
- const currentRecordFields = useRecoilComponentValue(
- currentRecordFieldsComponentState,
- recordTableId,
- );
-
- const { unfocusRecordTableCell } = useUnfocusRecordTableCell(recordTableId);
-
- const { handleColumnsChange } = useHandleColumnsChange();
-
- const { updateRecordField } = useUpdateRecordField(recordTableId);
- const { upsertRecordField } = useUpsertRecordField(recordTableId);
-
- const { saveViewFields } = useSaveCurrentViewFields();
-
- const { updateTableColumn } = useUpdateTableColumn(recordTableId);
- const { createTableColumn } = useCreateTableColumn(recordTableId);
-
- const handleColumnVisibilityChange = useCallback(
- async (
- viewField: Pick<
- ColumnDefinition,
- 'fieldMetadataId' | 'isVisible'
- >,
- ) => {
- const lastPosition = currentRecordFields.toSorted(
- sortByProperty('position', 'desc'),
- )[0].position;
-
- const shouldShowFieldMetadataItem = viewField.isVisible === true;
- const corresponingRecordField = currentRecordFields.find(
- (recordFieldToFind) =>
- recordFieldToFind.fieldMetadataItemId === viewField.fieldMetadataId,
- );
-
- const noExistingRecordField = !isDefined(corresponingRecordField);
-
- if (noExistingRecordField) {
- const recordFieldToUpsert: RecordField = {
- id: v4(),
- fieldMetadataItemId: viewField.fieldMetadataId,
- size: 100,
- isVisible: shouldShowFieldMetadataItem,
- position: lastPosition + 1,
- };
-
- upsertRecordField(recordFieldToUpsert);
-
- createTableColumn(viewField.fieldMetadataId, {
- position: lastPosition + 1,
- size: 100,
- isVisible: shouldShowFieldMetadataItem,
- });
-
- saveViewFields([mapRecordFieldToViewField(recordFieldToUpsert)]);
- } else {
- updateRecordField(viewField.fieldMetadataId, {
- isVisible: shouldShowFieldMetadataItem,
- });
-
- const updatedRecordField: RecordField = {
- ...corresponingRecordField,
- isVisible: shouldShowFieldMetadataItem,
- };
-
- updateTableColumn(viewField.fieldMetadataId, {
- isVisible: shouldShowFieldMetadataItem,
- });
-
- saveViewFields([mapRecordFieldToViewField(updatedRecordField)]);
- }
- },
- [
- updateTableColumn,
- saveViewFields,
- currentRecordFields,
- upsertRecordField,
- updateRecordField,
- createTableColumn,
- ],
- );
-
- const { moveRecordField } = useMoveRecordField(recordTableId);
-
- const handleMoveTableColumn = useCallback(
- async (
- direction: 'left' | 'right',
- column: ColumnDefinition,
- ) => {
- unfocusRecordTableCell();
-
- moveRecordField({
- direction: direction === 'left' ? 'before' : 'after',
- fieldMetadataItemIdToMove: column.fieldMetadataId,
- });
- },
- [unfocusRecordTableCell, moveRecordField],
- );
-
- return {
- handleColumnVisibilityChange,
- handleMoveTableColumn,
- handleColumnsChange,
- };
-};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyLoading.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyLoading.tsx
index 3a3d4c54a1..2765ebb37c 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyLoading.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyLoading.tsx
@@ -1,16 +1,13 @@
+import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableRowContextProvider } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { RecordTableRowDraggableContextProvider } from '@/object-record/record-table/contexts/RecordTableRowDraggableContext';
import { RecordTableCellCheckbox } from '@/object-record/record-table/record-table-cell/components/RecordTableCellCheckbox';
import { RecordTableCellGrip } from '@/object-record/record-table/record-table-cell/components/RecordTableCellGrip';
import { RecordTableCellLoading } from '@/object-record/record-table/record-table-cell/components/RecordTableCellLoading';
import { RecordTableTr } from '@/object-record/record-table/record-table-row/components/RecordTableTr';
-import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
-import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
export const RecordTableBodyLoading = () => {
- const visibleTableColumns = useRecoilComponentValue(
- visibleTableColumnsComponentSelector,
- );
+ const { visibleRecordFields } = useRecordTableContextOrThrow();
return (
@@ -41,8 +38,8 @@ export const RecordTableBodyLoading = () => {
>
- {visibleTableColumns.map((column) => (
-
+ {visibleRecordFields.map((recordField) => (
+
))}
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextGeneric.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextGeneric.tsx
index 76830780f2..a66d086441 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextGeneric.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextGeneric.tsx
@@ -1,41 +1,48 @@
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
import { isRecordFieldReadOnly } from '@/object-record/read-only/utils/isRecordFieldReadOnly';
+import { type RecordField } from '@/object-record/record-field/types/RecordField';
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
import { isFieldRelationFromManyObjects } from '@/object-record/record-field/ui/types/guards/isFieldRelationFromManyObjects';
import { isFieldRelationToOneObject } from '@/object-record/record-field/ui/types/guards/isFieldRelationToOneObject';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { RecordUpdateContext } from '@/object-record/record-table/contexts/EntityUpdateMutationHookContext';
-import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { useContext, type ReactNode } from 'react';
type RecordTableCellFieldContextGenericProps = {
+ recordField: RecordField;
children: ReactNode;
};
export const RecordTableCellFieldContextGeneric = ({
+ recordField,
children,
}: RecordTableCellFieldContextGenericProps) => {
const { recordId, isRecordReadOnly } = useRecordTableRowContextOrThrow();
- const { objectMetadataItem, objectPermissions } =
- useRecordTableContextOrThrow();
+ const {
+ objectMetadataItem,
+ objectPermissions,
+ fieldDefinitionByFieldMetadataItemId,
+ } = useRecordTableContextOrThrow();
const { indexIdentifierUrl, objectPermissionsByObjectMetadataId } =
useRecordIndexContextOrThrow();
- const { columnDefinition } = useContext(RecordTableCellContext);
+
+ const fieldDefinition =
+ fieldDefinitionByFieldMetadataItemId[recordField.fieldMetadataItemId];
const updateRecord = useContext(RecordUpdateContext);
let hasObjectReadPermissions = objectPermissions.canReadObjectRecords;
// todo @guillim : adjust this to handle morph relations permissions display
if (
- isFieldRelationToOneObject(columnDefinition) ||
- isFieldRelationFromManyObjects(columnDefinition)
+ isFieldRelationToOneObject(fieldDefinition) ||
+ isFieldRelationFromManyObjects(fieldDefinition)
) {
const relationObjectMetadataId =
- columnDefinition.metadata.relationObjectMetadataId;
+ fieldDefinition.metadata.relationObjectMetadataId;
const relationObjectPermissions = getObjectPermissionsForObject(
objectPermissionsByObjectMetadataId,
@@ -49,13 +56,13 @@ export const RecordTableCellFieldContextGeneric = ({
[updateRecord, {}],
labelIdentifierLink: indexIdentifierUrl(recordId),
isLabelIdentifier: isLabelIdentifierField({
fieldMetadataItem: {
- id: columnDefinition.fieldMetadataId,
- name: columnDefinition.metadata.fieldName,
+ id: fieldDefinition.fieldMetadataId,
+ name: fieldDefinition.metadata.fieldName,
},
objectMetadataItem,
}),
@@ -64,8 +71,8 @@ export const RecordTableCellFieldContextGeneric = ({
isRecordReadOnly: isRecordReadOnly ?? false,
objectPermissions,
fieldMetadataItem: {
- id: columnDefinition.fieldMetadataId,
- isUIReadOnly: columnDefinition.metadata.isUIReadOnly ?? false,
+ id: fieldDefinition.fieldMetadataId,
+ isUIReadOnly: fieldDefinition.metadata.isUIReadOnly ?? false,
},
}),
isForbidden: !hasObjectReadPermissions,
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextLabelIdentifier.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextLabelIdentifier.tsx
index fa03483291..0fda493f41 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextLabelIdentifier.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextLabelIdentifier.tsx
@@ -27,8 +27,10 @@ export const RecordTableCellFieldContextLabelIdentifier = ({
const { indexIdentifierUrl, objectPermissionsByObjectMetadataId } =
useRecordIndexContextOrThrow();
const { recordId, isRecordReadOnly } = useRecordTableRowContextOrThrow();
+ const { fieldDefinitionByFieldMetadataItemId } =
+ useRecordTableContextOrThrow();
- const { columnDefinition } = useContext(RecordTableCellContext);
+ const { recordField } = useContext(RecordTableCellContext);
const { objectMetadataItem, recordTableId } = useRecordTableContextOrThrow();
const { rowIndex } = useRecordTableRowContextOrThrow();
const { activateRecordTableRow } = useActiveRecordTableRow(recordTableId);
@@ -59,11 +61,14 @@ export const RecordTableCellFieldContextLabelIdentifier = ({
? 'CLICK'
: 'MOUSE_DOWN';
+ const fieldDefinition =
+ fieldDefinitionByFieldMetadataItemId[recordField.fieldMetadataItemId];
+
return (
[updateRecord, {}],
labelIdentifierLink: indexIdentifierUrl(recordId),
isLabelIdentifier: true,
@@ -73,11 +78,11 @@ export const RecordTableCellFieldContextLabelIdentifier = ({
isRecordReadOnly: isRecordReadOnly ?? false,
objectPermissions,
fieldMetadataItem: {
- id: columnDefinition.fieldMetadataId,
- isUIReadOnly: columnDefinition.metadata.isUIReadOnly ?? false,
+ id: recordField.fieldMetadataItemId,
+ isUIReadOnly: fieldDefinition.metadata.isUIReadOnly ?? false,
},
}),
- maxWidth: columnDefinition.size,
+ maxWidth: recordField.size,
onRecordChipClick: () => {
activateRecordTableRow(rowIndex);
unfocusRecordTableRow();
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextWrapper.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextWrapper.tsx
index dc3fe15a8a..e9c2cc5324 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextWrapper.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextWrapper.tsx
@@ -1,40 +1,40 @@
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
+import { type RecordField } from '@/object-record/record-field/types/RecordField';
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext';
import { RECORD_TABLE_CELL_INPUT_ID_PREFIX } from '@/object-record/record-table/constants/RecordTableCellInputIdPrefix';
-import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { RecordTableCellFieldContextGeneric } from '@/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextGeneric';
import { RecordTableCellFieldContextLabelIdentifier } from '@/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextLabelIdentifier';
import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId';
-import { useContext, type ReactNode } from 'react';
-import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
+import { type ReactNode } from 'react';
type RecordTableCellFieldContextWrapperProps = {
children: ReactNode;
+ recordField: RecordField;
};
export const RecordTableCellFieldContextWrapper = ({
+ recordField,
children,
}: RecordTableCellFieldContextWrapperProps) => {
- const { columnDefinition } = useContext(RecordTableCellContext);
const { recordId } = useRecordTableRowContextOrThrow();
- const { objectMetadataItem } = useRecordTableContextOrThrow();
+ const { objectMetadataItem, fieldMetadataItemByFieldMetadataItemId } =
+ useRecordTableContextOrThrow();
- if (isUndefinedOrNull(columnDefinition)) {
- return null;
- }
+ const fieldMetadataItem =
+ fieldMetadataItemByFieldMetadataItemId[recordField.fieldMetadataItemId];
const instanceId = getRecordFieldInputInstanceId({
recordId,
- fieldName: columnDefinition.metadata.fieldName,
+ fieldName: fieldMetadataItem.name,
prefix: RECORD_TABLE_CELL_INPUT_ID_PREFIX,
});
const isLabelIdentifier = isLabelIdentifierField({
fieldMetadataItem: {
- id: columnDefinition.fieldMetadataId,
- name: columnDefinition.metadata.fieldName,
+ id: recordField.fieldMetadataItemId,
+ name: fieldMetadataItem.name,
},
objectMetadataItem,
});
@@ -46,7 +46,10 @@ export const RecordTableCellFieldContextWrapper = ({
{children}
) : (
-
+
{children}
)}
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellPortalWrapper.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellPortalWrapper.tsx
index eae21201f2..71893c5f5c 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellPortalWrapper.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellPortalWrapper.tsx
@@ -1,11 +1,11 @@
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage';
import { useIsRecordReadOnly } from '@/object-record/read-only/hooks/useIsRecordReadOnly';
+import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector';
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { RecordTableRowContextProvider } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { RecordTableCellFieldContextWrapper } from '@/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextWrapper';
-import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
import { type TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { createPortal } from 'react-dom';
@@ -28,8 +28,8 @@ export const RecordTableCellPortalWrapper = ({
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
- const visibleTableColumns = useRecoilComponentValue(
- visibleTableColumnsComponentSelector,
+ const visibleRecordFields = useRecoilComponentValue(
+ visibleRecordFieldsComponentSelector,
);
const recordId = allRecordIds.at(position.row);
@@ -62,11 +62,13 @@ export const RecordTableCellPortalWrapper = ({
>
-
+
{children}
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellWrapper.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellWrapper.tsx
index d679d9420b..e4d967626e 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellWrapper.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/components/RecordTableCellWrapper.tsx
@@ -1,39 +1,38 @@
-import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
+import { type RecordField } from '@/object-record/record-field/types/RecordField';
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { RecordTableCellFieldContextWrapper } from '@/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextWrapper';
-import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { type TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
import { useMemo } from 'react';
export const RecordTableCellWrapper = ({
children,
- column,
- columnIndex,
+ recordField,
+ recordFieldIndex,
}: {
- column: ColumnDefinition;
- columnIndex: number;
+ recordField: RecordField;
+ recordFieldIndex: number;
children: React.ReactNode;
}) => {
const { rowIndex } = useRecordTableRowContextOrThrow();
const currentTableCellPosition: TableCellPosition = useMemo(
() => ({
- column: columnIndex,
+ column: recordFieldIndex,
row: rowIndex,
}),
- [columnIndex, rowIndex],
+ [recordFieldIndex, rowIndex],
);
return (
-
+
{children}
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/__mocks__/cell.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/__mocks__/cell.ts
index 37809cda48..a09b668b8a 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/__mocks__/cell.ts
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/__mocks__/cell.ts
@@ -1,7 +1,6 @@
import { RecordTableCellContextValue } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { RecordTableRowContextValue } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { RecordTableRowDraggableContextValue } from '@/object-record/record-table/contexts/RecordTableRowDraggableContext';
-import { FieldMetadataType } from '~/generated-metadata/graphql';
export const recordTableRowContextValue: RecordTableRowContextValue = {
rowIndex: 2,
@@ -18,17 +17,12 @@ export const recordTableRowDraggableContextValue: RecordTableRowDraggableContext
};
export const recordTableCellContextValue: RecordTableCellContextValue = {
- columnDefinition: {
+ recordField: {
size: 1,
position: 1,
- fieldMetadataId: 'fieldMetadataId',
- label: 'label',
- iconName: 'iconName',
- type: FieldMetadataType.TEXT,
- metadata: {
- placeHolder: 'placeHolder',
- fieldName: 'fieldName',
- },
+ fieldMetadataItemId: 'fieldMetadataId',
+ id: 'id',
+ isVisible: true,
},
cellPosition: {
row: 2,
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellInGroup.test.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellInGroup.test.tsx
index 0d42fae0bc..5a07f0ad5b 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellInGroup.test.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellInGroup.test.tsx
@@ -3,6 +3,7 @@ import { RecoilRoot } from 'recoil';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
+import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper';
import { textfieldDefinition } from '@/object-record/record-field/ui/__mocks__/fieldDefinitions';
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
@@ -29,34 +30,36 @@ const Wrapper = ({ children }: { children: React.ReactNode }) => (
snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems);
}}
>
-
-
-
+
+
-
-
-
+
+
- {children}
-
-
-
-
-
-
+
+ {children}
+
+
+
+
+
+
+
);
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellNoGroup.test.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellNoGroup.test.tsx
index f6cb3cad54..ceafd8e230 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellNoGroup.test.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/internal/__tests__/useCloseRecordTableCellNoGroup.test.tsx
@@ -3,6 +3,7 @@ import { RecoilRoot } from 'recoil';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
+import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper';
import { textfieldDefinition } from '@/object-record/record-field/ui/__mocks__/fieldDefinitions';
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
@@ -35,26 +36,30 @@ const Wrapper = ({ children }: { children: React.ReactNode }) => (
viewBarId="viewBarId"
objectNameSingular={CoreObjectNameSingular.Person}
>
-
-
-
-
+
+
- {children}
-
-
-
-
+
+ {children}
+
+
+
+
+
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableAggregateFooter.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableAggregateFooter.tsx
index 37620bc9b9..5338d6adc3 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableAggregateFooter.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableAggregateFooter.tsx
@@ -1,12 +1,11 @@
import styled from '@emotion/styled';
import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex';
+import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableAggregateFooterCell } from '@/object-record/record-table/record-table-footer/components/RecordTableAggregateFooterCell';
import { RecordTableColumnAggregateFooterCellContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterCellContext';
import { FIRST_TH_WIDTH } from '@/object-record/record-table/record-table-header/components/RecordTableHeader';
-import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
import { useScrollWrapperElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperElement';
-import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { isUndefined } from '@sniptt/guards';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
@@ -86,9 +85,7 @@ export const RecordTableAggregateFooter = ({
}: {
currentRecordGroupId?: string;
}) => {
- const visibleTableColumns = useRecoilComponentValue(
- visibleTableColumnsComponentSelector,
- );
+ const { visibleRecordFields } = useRecordTableContextOrThrow();
const { scrollWrapperHTMLElement } = useScrollWrapperElement();
@@ -105,13 +102,13 @@ export const RecordTableAggregateFooter = ({
}
>
- {visibleTableColumns.map((column, index) => {
+ {visibleRecordFields.map((recordField, index) => {
return (
);
})}
- |
+ |
|
|
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableAggregateFooterCell.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableAggregateFooterCell.tsx
index 5d21fa47ad..88ab0ed589 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableAggregateFooterCell.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-footer/components/RecordTableAggregateFooterCell.tsx
@@ -1,11 +1,10 @@
import styled from '@emotion/styled';
-import { useContext, useMemo } from 'react';
+import { useContext } from 'react';
+import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableColumnAggregateFooterCellContext } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterCellContext';
import { RecordTableColumnFooterWithDropdown } from '@/object-record/record-table/record-table-footer/components/RecordTableColumnAggregateFooterWithDropdown';
-import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
-import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
-import { mapArrayToObject } from '~/utils/array/mapArrayToObject';
+import { findById, isDefined } from 'twenty-shared/utils';
const COLUMN_MIN_WIDTH = 104;
@@ -59,22 +58,21 @@ export const RecordTableAggregateFooterCell = ({
isFirstCell?: boolean;
currentRecordGroupId?: string;
}) => {
- const tableColumns = useRecoilComponentValue(tableColumnsComponentState);
- const tableColumnsByKey = useMemo(
- () =>
- mapArrayToObject(tableColumns, ({ fieldMetadataId }) => fieldMetadataId),
- [tableColumns],
- );
+ const { visibleRecordFields } = useRecordTableContextOrThrow();
+
const { fieldMetadataId } = useContext(
RecordTableColumnAggregateFooterCellContext,
);
+ const recordField = visibleRecordFields.find(findById(fieldMetadataId));
+
+ if (!isDefined(recordField)) {
+ return null;
+ }
+
return (
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHead.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHead.tsx
index a0654bffe2..05d2b3b0b9 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHead.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHead.tsx
@@ -1,17 +1,15 @@
import { css, useTheme } from '@emotion/react';
import styled from '@emotion/styled';
-import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState';
+import { fieldMetadataItemByIdSelector } from '@/object-metadata/states/fieldMetadataItemByIdSelector';
+import { isFieldMetadataItemLabelIdentifierSelector } from '@/object-metadata/states/isFieldMetadataItemLabelIdentifierSelector';
+import { type RecordField } from '@/object-record/record-field/types/RecordField';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
+import { useRecoilValue } from 'recoil';
import { useIcons } from 'twenty-ui/display';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
-import { type ColumnDefinition } from '../../types/ColumnDefinition';
-
-type RecordTableColumnHeadProps = {
- column: ColumnDefinition;
-};
const StyledTitle = styled.div<{ hideTitle?: boolean }>`
align-items: center;
@@ -48,26 +46,44 @@ const StyledText = styled.span`
white-space: nowrap;
`;
+type RecordTableColumnHeadProps = {
+ recordField: RecordField;
+};
+
export const RecordTableColumnHead = ({
- column,
+ recordField,
}: RecordTableColumnHeadProps) => {
const theme = useTheme();
- const { getIcon } = useIcons();
- const Icon = getIcon(column.iconName);
+ const correspondingFieldMetadataItem = useRecoilValue(
+ fieldMetadataItemByIdSelector({
+ fieldMetadataItemId: recordField.fieldMetadataItemId,
+ }),
+ );
const isRecordTableScrolledLeft = useRecoilComponentValue(
isRecordTableScrolledLeftComponentState,
);
+ const { getIcon } = useIcons();
+ const Icon = getIcon(
+ correspondingFieldMetadataItem.foundFieldMetadataItem?.icon,
+ );
+
+ const isLabelIdentifier = useRecoilValue(
+ isFieldMetadataItemLabelIdentifierSelector({
+ fieldMetadataItemId: recordField.fieldMetadataItemId,
+ }),
+ );
+
return (
-
+
- {column.label}
+
+ {correspondingFieldMetadataItem.foundFieldMetadataItem?.label}
+
);
};
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadDropdownMenu.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadDropdownMenu.tsx
index 3e9e308792..05270a5ebb 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadDropdownMenu.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadDropdownMenu.tsx
@@ -1,17 +1,20 @@
-import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
+import { isFieldMetadataItemFilterableAndSortableSelector } from '@/object-metadata/states/isFieldMetadataItemFilterableAndSortableSelector';
+import { isFieldMetadataItemLabelIdentifierSelector } from '@/object-metadata/states/isFieldMetadataItemLabelIdentifierSelector';
+import { useChangeRecordFieldVisibility } from '@/object-record/record-field/hooks/useChangeRecordFieldVisibility';
+import { type RecordField } from '@/object-record/record-field/types/RecordField';
import { useHandleToggleColumnSort } from '@/object-record/record-index/hooks/useHandleToggleColumnSort';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
+import { useMoveTableColumn } from '@/object-record/record-table/hooks/useMoveTableColumn';
import { useOpenRecordFilterChipFromTableHeader } from '@/object-record/record-table/record-table-header/hooks/useOpenRecordFilterChipFromTableHeader';
-import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { useToggleScrollWrapper } from '@/ui/utilities/scroll/hooks/useToggleScrollWrapper';
-import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
+import { useRecoilValue } from 'recoil';
import {
IconArrowLeft,
IconArrowRight,
@@ -20,11 +23,9 @@ import {
IconSortDescending,
} from 'twenty-ui/display';
import { MenuItem } from 'twenty-ui/navigation';
-import { useTableColumns } from '../../hooks/useTableColumns';
-import { type ColumnDefinition } from '../../types/ColumnDefinition';
export type RecordTableColumnHeadDropdownMenuProps = {
- column: ColumnDefinition;
+ recordField: RecordField;
objectMetadataId: string;
};
@@ -33,7 +34,7 @@ const StyledDropdownMenuItemsContainer = styled(DropdownMenuItemsContainer)`
`;
export const RecordTableColumnHeadDropdownMenu = ({
- column,
+ recordField,
objectMetadataId,
}: RecordTableColumnHeadDropdownMenuProps) => {
const { t } = useLingui();
@@ -41,25 +42,37 @@ export const RecordTableColumnHeadDropdownMenu = ({
const { toggleScrollXWrapper, toggleScrollYWrapper } =
useToggleScrollWrapper();
- const visibleTableColumns = useRecoilComponentValue(
- visibleTableColumnsComponentSelector,
+ const { visibleRecordFields } = useRecordTableContextOrThrow();
+
+ const isLabelIdentifier = useRecoilValue(
+ isFieldMetadataItemLabelIdentifierSelector({
+ fieldMetadataItemId: recordField.fieldMetadataItemId,
+ }),
);
- const secondVisibleColumn = visibleTableColumns[1];
- const canMove = column.isLabelIdentifier !== true;
+ const secondVisibleRecordField = visibleRecordFields[1];
+ const canMove = isLabelIdentifier !== true;
const canMoveLeft =
- column.fieldMetadataId !== secondVisibleColumn?.fieldMetadataId && canMove;
+ recordField.fieldMetadataItemId !==
+ secondVisibleRecordField?.fieldMetadataItemId && canMove;
+
+ const lastVisibleRecordField =
+ visibleRecordFields[visibleRecordFields.length - 1];
- const lastVisibleColumn = visibleTableColumns[visibleTableColumns.length - 1];
const canMoveRight =
- column.fieldMetadataId !== lastVisibleColumn?.fieldMetadataId && canMove;
+ recordField.fieldMetadataItemId !==
+ lastVisibleRecordField?.fieldMetadataItemId && canMove;
const { recordTableId } = useRecordTableContextOrThrow();
- const { handleColumnVisibilityChange, handleMoveTableColumn } =
- useTableColumns({ recordTableId });
+ const { moveTableColumn } = useMoveTableColumn({
+ recordTableId,
+ });
- const dropdownId = column.fieldMetadataId + '-header';
+ const { changeRecordFieldVisibility } =
+ useChangeRecordFieldVisibility(recordTableId);
+
+ const dropdownId = recordField.fieldMetadataItemId + '-header';
const { closeDropdown } = useCloseDropdown();
@@ -71,9 +84,10 @@ export const RecordTableColumnHeadDropdownMenu = ({
const handleColumnMoveLeft = () => {
closeDropdownAndToggleScroll();
+
if (!canMoveLeft) return;
- handleMoveTableColumn('left', column);
+ moveTableColumn('left', recordField.fieldMetadataItemId);
};
const handleColumnMoveRight = () => {
@@ -81,13 +95,13 @@ export const RecordTableColumnHeadDropdownMenu = ({
if (!canMoveRight) return;
- handleMoveTableColumn('right', column);
+ moveTableColumn('right', recordField.fieldMetadataItemId);
};
const handleColumnVisibility = () => {
closeDropdownAndToggleScroll();
- handleColumnVisibilityChange({
- ...column,
+ changeRecordFieldVisibility({
+ fieldMetadataId: recordField.fieldMetadataItemId,
isVisible: false,
});
};
@@ -99,7 +113,7 @@ export const RecordTableColumnHeadDropdownMenu = ({
const handleSortClick = () => {
closeDropdownAndToggleScroll();
- handleToggleColumnSort(column.fieldMetadataId);
+ handleToggleColumnSort(recordField.fieldMetadataItemId);
};
const { openRecordFilterChipFromTableHeader } =
@@ -108,14 +122,18 @@ export const RecordTableColumnHeadDropdownMenu = ({
const handleFilterClick = () => {
closeDropdownAndToggleScroll();
- openRecordFilterChipFromTableHeader(column.fieldMetadataId);
+ openRecordFilterChipFromTableHeader(recordField.fieldMetadataItemId);
};
- const isSortable = column.isSortable === true;
- const isFilterable = column.isFilterable === true;
+ const { isFilterable, isSortable } = useRecoilValue(
+ isFieldMetadataItemFilterableAndSortableSelector({
+ fieldMetadataItemId: recordField.fieldMetadataItemId,
+ }),
+ );
+
const showSeparator =
- (isFilterable || isSortable) && column.isLabelIdentifier !== true;
- const canHide = column.isLabelIdentifier !== true;
+ (isFilterable || isSortable) && isLabelIdentifier !== true;
+ const canHide = isLabelIdentifier !== true;
return (
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown.tsx
index e7fda38f1a..17ad06324e 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown.tsx
@@ -1,5 +1,4 @@
-import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
-import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
+import { type RecordField } from '@/object-record/record-field/types/RecordField';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { useToggleScrollWrapper } from '@/ui/utilities/scroll/hooks/useToggleScrollWrapper';
import { useCallback } from 'react';
@@ -7,13 +6,13 @@ import { RecordTableColumnHead } from './RecordTableColumnHead';
import { RecordTableColumnHeadDropdownMenu } from './RecordTableColumnHeadDropdownMenu';
type RecordTableColumnHeadWithDropdownProps = {
- column: ColumnDefinition;
+ recordField: RecordField;
objectMetadataId: string;
};
export const RecordTableColumnHeadWithDropdown = ({
objectMetadataId,
- column,
+ recordField,
}: RecordTableColumnHeadWithDropdownProps) => {
const { toggleScrollXWrapper, toggleScrollYWrapper } =
useToggleScrollWrapper();
@@ -32,11 +31,11 @@ export const RecordTableColumnHeadWithDropdown = ({
}
+ dropdownId={recordField.fieldMetadataItemId + '-header'}
+ clickableComponent={}
dropdownComponents={
}
diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeader.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeader.tsx
index 9e8c024b17..563be9bbd8 100644
--- a/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeader.tsx
+++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-header/components/RecordTableHeader.tsx
@@ -80,15 +80,18 @@ const StyledTableHead = styled.thead`
`;
export const RecordTableHeader = () => {
- const { visibleTableColumns } = useRecordTableContextOrThrow();
+ const { visibleRecordFields } = useRecordTableContextOrThrow();
return (