Refactor field definition and column definition manipulation with record field (#13992)
This PR introduces the usage of record fields in the manipulation of table columns and board fields. Since all of the actual system relies on states like tableColumns, recordIndexFieldDefinitions and the likes, it was required to implement temporary utils that modifies those states in parallel of the new generic currentRecordFields, to keep a working product. With this PR though, currentRecordFields becomes the single source of truth that gets saved to DB, and the remaining work is just to make the switch with this new state on all components that are plugged to tableColumns and the like. This will be done in another PR.
This commit is contained in:
+29
@@ -0,0 +1,29 @@
|
||||
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
||||
import { TABLE_COLUMNS_DENY_LIST } from '@/object-record/constants/TableColumnsDenyList';
|
||||
import { useMemo } from 'react';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
export const useAvailableFieldMetadataItems = ({
|
||||
objectMetadataItemId,
|
||||
}: {
|
||||
objectMetadataItemId: string;
|
||||
}) => {
|
||||
const { objectMetadataItem } = useObjectMetadataItemById({
|
||||
objectId: objectMetadataItemId,
|
||||
});
|
||||
|
||||
const availableFieldMetadataItems = useMemo(
|
||||
() =>
|
||||
objectMetadataItem.readableFields.filter((fieldMetadataItemToFilter) => {
|
||||
return (
|
||||
fieldMetadataItemToFilter.isActive &&
|
||||
!fieldMetadataItemToFilter.isSystem &&
|
||||
!TABLE_COLUMNS_DENY_LIST.includes(fieldMetadataItemToFilter.name) &&
|
||||
fieldMetadataItemToFilter.type !== FieldMetadataType.UUID
|
||||
);
|
||||
}),
|
||||
[objectMetadataItem],
|
||||
);
|
||||
|
||||
return { availableFieldMetadataItems };
|
||||
};
|
||||
+18
@@ -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 flattenedReadableFieldMetadataItemsSelector = selector<
|
||||
FieldMetadataItem[]
|
||||
>({
|
||||
key: 'flattenedReadableFieldMetadataItemsSelector',
|
||||
get: ({ get }) => {
|
||||
const objectMetadataItems = get(objectMetadataItemsState);
|
||||
|
||||
const flattenedReadableFieldMetadataItems = objectMetadataItems.flatMap(
|
||||
(objectMetadataItem) => objectMetadataItem.readableFields,
|
||||
);
|
||||
|
||||
return flattenedReadableFieldMetadataItems;
|
||||
},
|
||||
});
|
||||
-119
@@ -1,119 +0,0 @@
|
||||
import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard';
|
||||
import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/states/recordIndexFieldDefinitionsState';
|
||||
import { type DropResult, type ResponderProvided } from '@hello-pangea/dnd';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
|
||||
|
||||
jest.mock('@/views/hooks/useSaveCurrentViewFields', () => ({
|
||||
useSaveCurrentViewFields: jest.fn(() => ({
|
||||
saveViewFields: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('@/views/hooks/useUpdateCurrentView', () => ({
|
||||
useUpdateCurrentView: jest.fn(() => ({
|
||||
updateCurrentView: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
const objectNameSingular = 'company';
|
||||
|
||||
describe('useObjectOptionsForBoard', () => {
|
||||
const mockObjectMetadataItem = generatedMockObjectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.nameSingular === objectNameSingular,
|
||||
);
|
||||
|
||||
if (!mockObjectMetadataItem) {
|
||||
throw new Error('Mock object metadata item not found');
|
||||
}
|
||||
|
||||
const mockFieldMetadataItem1 = mockObjectMetadataItem.fields.find(
|
||||
(field) => field.name === 'name',
|
||||
);
|
||||
|
||||
if (!mockFieldMetadataItem1) {
|
||||
throw new Error('Mock field metadata item not found for "name"');
|
||||
}
|
||||
|
||||
const mockFieldMetadataItem2 = mockObjectMetadataItem.fields.find(
|
||||
(field) => field.name === 'createdAt',
|
||||
);
|
||||
|
||||
if (!mockFieldMetadataItem2) {
|
||||
throw new Error('Mock field metadata item not found for "createdAt"');
|
||||
}
|
||||
|
||||
const initialRecoilState = [
|
||||
{
|
||||
fieldMetadataId: mockFieldMetadataItem1.id,
|
||||
isVisible: true,
|
||||
position: 0,
|
||||
},
|
||||
{
|
||||
fieldMetadataId: mockFieldMetadataItem2.id,
|
||||
isVisible: true,
|
||||
position: 1,
|
||||
},
|
||||
];
|
||||
|
||||
const renderWithRecoil = () =>
|
||||
renderHook(
|
||||
() =>
|
||||
useObjectOptionsForBoard({
|
||||
objectNameSingular,
|
||||
recordBoardId: 'boardId',
|
||||
viewBarId: 'viewBarId',
|
||||
}),
|
||||
{
|
||||
wrapper: getJestMetadataAndApolloMocksAndActionMenuWrapper({
|
||||
apolloMocks: [],
|
||||
onInitializeRecoilSnapshot: (snapshot) => {
|
||||
snapshot.set(
|
||||
recordIndexFieldDefinitionsState,
|
||||
initialRecoilState as any,
|
||||
);
|
||||
},
|
||||
componentInstanceId: 'test',
|
||||
contextStoreCurrentObjectMetadataNameSingular: objectNameSingular,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
it('reorders fields correctly', () => {
|
||||
const { result } = renderWithRecoil();
|
||||
|
||||
const dropResult: DropResult = {
|
||||
source: { droppableId: 'droppable', index: 1 },
|
||||
destination: { droppableId: 'droppable', index: 2 },
|
||||
draggableId: mockFieldMetadataItem1.id,
|
||||
type: 'TYPE',
|
||||
mode: 'FLUID',
|
||||
reason: 'DROP',
|
||||
combine: null,
|
||||
};
|
||||
|
||||
const responderProvided: ResponderProvided = {
|
||||
announce: jest.fn(),
|
||||
};
|
||||
|
||||
act(() => {
|
||||
result.current.handleReorderBoardFields(dropResult, responderProvided);
|
||||
});
|
||||
|
||||
expect(result.current.visibleBoardFields).toEqual([
|
||||
{
|
||||
fieldMetadataId: mockFieldMetadataItem2.id,
|
||||
isVisible: true,
|
||||
position: 0,
|
||||
},
|
||||
{
|
||||
fieldMetadataId: mockFieldMetadataItem1.id,
|
||||
isVisible: true,
|
||||
position: 1,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
+122
-50
@@ -5,17 +5,25 @@ import { useRecoilState } from 'recoil';
|
||||
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState';
|
||||
import { useReorderVisibleRecordFields } from '@/object-record/record-field/hooks/useReorderVisibleRecordFields';
|
||||
import { useUpdateRecordField } from '@/object-record/record-field/hooks/useUpdateRecordField';
|
||||
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 { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/states/recordIndexFieldDefinitionsState';
|
||||
import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useSaveCurrentViewFields } from '@/views/hooks/useSaveCurrentViewFields';
|
||||
import { useUpdateCurrentView } from '@/views/hooks/useUpdateCurrentView';
|
||||
import { type GraphQLView } from '@/views/types/GraphQLView';
|
||||
import { mapBoardFieldDefinitionsToViewFields } from '@/views/utils/mapBoardFieldDefinitionsToViewFields';
|
||||
import { mapRecordFieldToViewField } from '@/views/utils/mapRecordFieldToViewField';
|
||||
import { produce } from 'immer';
|
||||
import { findByProperty, isDefined } from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
import { mapArrayToObject } from '~/utils/array/mapArrayToObject';
|
||||
import { moveArrayItem } from '~/utils/array/moveArrayItem';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
import { sortByProperty } from '~/utils/array/sortByProperty';
|
||||
|
||||
type useObjectOptionsForBoardParams = {
|
||||
objectNameSingular: string;
|
||||
@@ -71,6 +79,9 @@ export const useObjectOptionsForBoard = ({
|
||||
[recordIndexFieldDefinitions],
|
||||
);
|
||||
|
||||
const { reorderVisibleRecordFields } =
|
||||
useReorderVisibleRecordFields(recordBoardId);
|
||||
|
||||
const hiddenBoardFields = useMemo(
|
||||
() =>
|
||||
availableColumnDefinitions
|
||||
@@ -97,24 +108,46 @@ export const useObjectOptionsForBoard = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const reorderedVisibleBoardFields = moveArrayItem(visibleBoardFields, {
|
||||
const updatedRecordField = reorderVisibleRecordFields({
|
||||
fromIndex: result.source.index - 1,
|
||||
toIndex: result.destination.index - 1,
|
||||
});
|
||||
|
||||
if (isDeeplyEqual(visibleBoardFields, reorderedVisibleBoardFields))
|
||||
return;
|
||||
saveViewFields([mapRecordFieldToViewField(updatedRecordField)]);
|
||||
|
||||
const updatedFields = [...reorderedVisibleBoardFields].map(
|
||||
(field, index) => ({ ...field, position: index }),
|
||||
const modifiedRecordIndexFieldDefinitions = produce(
|
||||
recordIndexFieldDefinitions,
|
||||
(draftRecordIndexFieldDefinitions) => {
|
||||
const indexToModify = draftRecordIndexFieldDefinitions.findIndex(
|
||||
(recordIndexFieldDefinitionToModify) =>
|
||||
recordIndexFieldDefinitionToModify.fieldMetadataId ===
|
||||
updatedRecordField.fieldMetadataItemId,
|
||||
);
|
||||
|
||||
draftRecordIndexFieldDefinitions[indexToModify].position =
|
||||
updatedRecordField.position;
|
||||
},
|
||||
);
|
||||
|
||||
setRecordIndexFieldDefinitions(updatedFields);
|
||||
saveViewFields(mapBoardFieldDefinitionsToViewFields(updatedFields));
|
||||
// TODO: remove after refactor
|
||||
setRecordIndexFieldDefinitions(modifiedRecordIndexFieldDefinitions);
|
||||
},
|
||||
[saveViewFields, setRecordIndexFieldDefinitions, visibleBoardFields],
|
||||
[
|
||||
saveViewFields,
|
||||
setRecordIndexFieldDefinitions,
|
||||
recordIndexFieldDefinitions,
|
||||
reorderVisibleRecordFields,
|
||||
],
|
||||
);
|
||||
|
||||
const currentRecordFields = useRecoilComponentValue(
|
||||
currentRecordFieldsComponentState,
|
||||
recordBoardId,
|
||||
);
|
||||
|
||||
const { updateRecordField } = useUpdateRecordField(recordBoardId);
|
||||
const { upsertRecordField } = useUpsertRecordField(recordBoardId);
|
||||
|
||||
// Todo : this seems over complex and should at least be extracted to an util with unit test.
|
||||
// Let's refactor this as we introduce the new viewBar
|
||||
const handleBoardFieldVisibilityChange = useCallback(
|
||||
@@ -124,59 +157,98 @@ export const useObjectOptionsForBoard = ({
|
||||
'fieldMetadataId' | 'isVisible'
|
||||
>,
|
||||
) => {
|
||||
const isNewViewField = !(
|
||||
updatedFieldDefinition.fieldMetadataId in
|
||||
recordIndexFieldDefinitionsByKey
|
||||
const lastPosition = currentRecordFields.toSorted(
|
||||
sortByProperty('position', 'desc'),
|
||||
)[0].position;
|
||||
|
||||
const shouldShowFieldMetadataItem =
|
||||
updatedFieldDefinition.isVisible === true;
|
||||
const corresponingRecordField = currentRecordFields.find(
|
||||
(recordFieldToFind) =>
|
||||
recordFieldToFind.fieldMetadataItemId ===
|
||||
updatedFieldDefinition.fieldMetadataId,
|
||||
);
|
||||
|
||||
let updatedFieldsDefinitions: ColumnDefinition<FieldMetadata>[];
|
||||
const noExistingRecordField = !isDefined(corresponingRecordField);
|
||||
|
||||
if (isNewViewField) {
|
||||
const correspondingFieldDefinition = availableColumnDefinitions.find(
|
||||
(availableColumnDefinition) =>
|
||||
availableColumnDefinition.fieldMetadataId ===
|
||||
updatedFieldDefinition.fieldMetadataId,
|
||||
);
|
||||
if (noExistingRecordField) {
|
||||
const recordFieldToUpsert: RecordField = {
|
||||
id: v4(),
|
||||
fieldMetadataItemId: updatedFieldDefinition.fieldMetadataId,
|
||||
size: 100,
|
||||
isVisible: shouldShowFieldMetadataItem,
|
||||
position: lastPosition + 1,
|
||||
};
|
||||
|
||||
if (!correspondingFieldDefinition) return;
|
||||
upsertRecordField(recordFieldToUpsert);
|
||||
|
||||
const lastVisibleBoardField =
|
||||
visibleBoardFields[visibleBoardFields.length - 1];
|
||||
saveViewFields([mapRecordFieldToViewField(recordFieldToUpsert)]);
|
||||
|
||||
updatedFieldsDefinitions = [
|
||||
...recordIndexFieldDefinitions,
|
||||
{
|
||||
...correspondingFieldDefinition,
|
||||
position: (lastVisibleBoardField?.position || 0) + 1,
|
||||
isVisible: true,
|
||||
const correspondingAvailableColumnDefinition =
|
||||
availableColumnDefinitions.find(
|
||||
findByProperty(
|
||||
'fieldMetadataId',
|
||||
updatedFieldDefinition.fieldMetadataId,
|
||||
),
|
||||
);
|
||||
|
||||
const modifiedRecordIndexFieldDefinitions = produce(
|
||||
recordIndexFieldDefinitions,
|
||||
(draftRecordIndexFieldDefinitions) => {
|
||||
if (!isDefined(correspondingAvailableColumnDefinition)) {
|
||||
throw new Error(
|
||||
`correspondingAvailableColumnDefinition is not defined this should not happen.`,
|
||||
);
|
||||
}
|
||||
|
||||
draftRecordIndexFieldDefinitions.push({
|
||||
...correspondingAvailableColumnDefinition,
|
||||
fieldMetadataId: updatedFieldDefinition.fieldMetadataId,
|
||||
isVisible: shouldShowFieldMetadataItem,
|
||||
});
|
||||
},
|
||||
];
|
||||
} else {
|
||||
updatedFieldsDefinitions = recordIndexFieldDefinitions.map(
|
||||
(existingFieldDefinition) =>
|
||||
existingFieldDefinition.fieldMetadataId ===
|
||||
updatedFieldDefinition.fieldMetadataId
|
||||
? {
|
||||
...existingFieldDefinition,
|
||||
isVisible: !existingFieldDefinition.isVisible,
|
||||
}
|
||||
: existingFieldDefinition,
|
||||
);
|
||||
|
||||
// TODO: remove after refactor
|
||||
setRecordIndexFieldDefinitions(modifiedRecordIndexFieldDefinitions);
|
||||
} else {
|
||||
updateRecordField(updatedFieldDefinition.fieldMetadataId, {
|
||||
isVisible: shouldShowFieldMetadataItem,
|
||||
});
|
||||
|
||||
const updatedRecordField: RecordField = {
|
||||
...corresponingRecordField,
|
||||
isVisible: shouldShowFieldMetadataItem,
|
||||
};
|
||||
|
||||
saveViewFields([mapRecordFieldToViewField(updatedRecordField)]);
|
||||
|
||||
const modifiedRecordIndexFieldDefinitions = produce(
|
||||
recordIndexFieldDefinitions,
|
||||
(draftRecordIndexFieldDefinitions) => {
|
||||
const indexToModify = draftRecordIndexFieldDefinitions.findIndex(
|
||||
(recordIndexFieldDefinitionToModify) =>
|
||||
recordIndexFieldDefinitionToModify.fieldMetadataId ===
|
||||
updatedRecordField.fieldMetadataItemId,
|
||||
);
|
||||
|
||||
draftRecordIndexFieldDefinitions[indexToModify].isVisible =
|
||||
shouldShowFieldMetadataItem;
|
||||
},
|
||||
);
|
||||
|
||||
// TODO: remove after refactor
|
||||
setRecordIndexFieldDefinitions(modifiedRecordIndexFieldDefinitions);
|
||||
}
|
||||
|
||||
setRecordIndexFieldDefinitions(updatedFieldsDefinitions);
|
||||
|
||||
saveViewFields(
|
||||
mapBoardFieldDefinitionsToViewFields(updatedFieldsDefinitions),
|
||||
);
|
||||
},
|
||||
[
|
||||
recordIndexFieldDefinitionsByKey,
|
||||
currentRecordFields,
|
||||
updateRecordField,
|
||||
upsertRecordField,
|
||||
setRecordIndexFieldDefinitions,
|
||||
saveViewFields,
|
||||
availableColumnDefinitions,
|
||||
visibleBoardFields,
|
||||
recordIndexFieldDefinitions,
|
||||
availableColumnDefinitions,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
+74
-19
@@ -1,12 +1,19 @@
|
||||
import { type OnDragEndResponder } from '@hello-pangea/dnd';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useReorderRecordFields } from '@/object-record/record-field/hooks/useReorderRecordFields';
|
||||
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 { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
||||
import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { moveArrayItem } from '~/utils/array/moveArrayItem';
|
||||
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,
|
||||
@@ -16,19 +23,37 @@ export const useObjectOptionsForTable = (
|
||||
hiddenTableColumnsComponentSelector,
|
||||
recordTableId,
|
||||
);
|
||||
const visibleTableColumns = useRecoilComponentValue(
|
||||
visibleTableColumnsComponentSelector,
|
||||
|
||||
const tableColumns = useRecoilComponentValue(
|
||||
tableColumnsComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const { handleColumnVisibilityChange, handleColumnReorder } = useTableColumns(
|
||||
{ recordTableId, objectMetadataId },
|
||||
const currentRecordFields = useRecoilComponentValue(
|
||||
currentRecordFieldsComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const { reorderRecordFields } = useReorderRecordFields();
|
||||
const { filterVisibleAndReadableRecordField } =
|
||||
useFilterVisibleAndReadableRecordField();
|
||||
|
||||
const handleReorderColumns: OnDragEndResponder = useCallback(
|
||||
async (result) => {
|
||||
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 ||
|
||||
@@ -37,25 +62,55 @@ export const useObjectOptionsForTable = (
|
||||
return;
|
||||
}
|
||||
|
||||
const reorderedFields = moveArrayItem(visibleTableColumns, {
|
||||
const updatedRecordField = reorderVisibleRecordFields({
|
||||
fromIndex: result.source.index - 1,
|
||||
toIndex: result.destination.index - 1,
|
||||
});
|
||||
|
||||
reorderRecordFields({
|
||||
fromIndex: result.source.index - 1,
|
||||
toIndex: result.destination.index - 1,
|
||||
});
|
||||
saveViewFields([mapRecordFieldToViewField(updatedRecordField)]);
|
||||
|
||||
handleColumnReorder(reorderedFields);
|
||||
// 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,
|
||||
);
|
||||
},
|
||||
[visibleTableColumns, handleColumnReorder, reorderRecordFields],
|
||||
[
|
||||
reorderVisibleRecordFields,
|
||||
saveViewFields,
|
||||
objectMetadataId,
|
||||
recordTableId,
|
||||
setTableColumns,
|
||||
tableColumns,
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
handleReorderColumns,
|
||||
handleColumnVisibilityChange,
|
||||
visibleTableColumns,
|
||||
visibleTableColumns: visibleRecordFields,
|
||||
hiddenTableColumns,
|
||||
};
|
||||
};
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
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<any>,
|
||||
'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,
|
||||
};
|
||||
};
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import { flattenedReadableFieldMetadataItemsSelector } from '@/object-metadata/states/flattenedReadableFieldMetadataItemIdsSelector';
|
||||
import { type RecordField } from '@/object-record/record-field/types/RecordField';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
export const useFilterVisibleAndReadableRecordField = () => {
|
||||
const flattenedReadableFieldMetadataItems = useRecoilValue(
|
||||
flattenedReadableFieldMetadataItemsSelector,
|
||||
);
|
||||
|
||||
const filterVisibleAndReadableRecordField = (
|
||||
recordFieldToFilter: RecordField,
|
||||
) => {
|
||||
return (
|
||||
recordFieldToFilter.isVisible === true &&
|
||||
flattenedReadableFieldMetadataItems.some(
|
||||
(fieldMetadataItemToFilter) =>
|
||||
fieldMetadataItemToFilter.id ===
|
||||
recordFieldToFilter.fieldMetadataItemId &&
|
||||
fieldMetadataItemToFilter.isActive === true &&
|
||||
fieldMetadataItemToFilter.isSystem !== true,
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return { filterVisibleAndReadableRecordField };
|
||||
};
|
||||
+50
-14
@@ -1,14 +1,26 @@
|
||||
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';
|
||||
import { mapRecordFieldToViewField } from '@/views/utils/mapRecordFieldToViewField';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { sortByProperty } from '~/utils/array/sortByProperty';
|
||||
|
||||
export const useMoveRecordField = () => {
|
||||
export const useMoveRecordField = (recordTableId?: string) => {
|
||||
const currentRecordFieldsCallbackState = useRecoilComponentCallbackState(
|
||||
currentRecordFieldsComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const { saveViewFields } = useSaveCurrentViewFields();
|
||||
|
||||
const { updateRecordField } = useUpdateRecordField(recordTableId);
|
||||
|
||||
const { updateTableColumn } = useUpdateTableColumn(recordTableId);
|
||||
|
||||
const moveRecordField = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
({ snapshot }) =>
|
||||
({
|
||||
direction,
|
||||
fieldMetadataItemIdToMove,
|
||||
@@ -20,7 +32,11 @@ export const useMoveRecordField = () => {
|
||||
.getLoadable(currentRecordFieldsCallbackState)
|
||||
.getValue();
|
||||
|
||||
const indexOfRecordFieldToMove = currentRecordFields.findIndex(
|
||||
const sortedRecordFields = currentRecordFields.toSorted(
|
||||
sortByProperty('position'),
|
||||
);
|
||||
|
||||
const indexOfRecordFieldToMove = sortedRecordFields.findIndex(
|
||||
(recordField) =>
|
||||
recordField.fieldMetadataItemId === fieldMetadataItemIdToMove,
|
||||
);
|
||||
@@ -29,7 +45,7 @@ export const useMoveRecordField = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const newRecordFields = [...currentRecordFields];
|
||||
const newRecordFields = [...sortedRecordFields];
|
||||
|
||||
const targetArrayIndex =
|
||||
direction === 'before'
|
||||
@@ -50,20 +66,40 @@ export const useMoveRecordField = () => {
|
||||
const targetRecordFieldNewPosition = currentRecordField.position;
|
||||
const currentRecordFieldNewPosition = targetRecordField.position;
|
||||
|
||||
newRecordFields[indexOfRecordFieldToMove] = {
|
||||
...newRecordFields[indexOfRecordFieldToMove],
|
||||
position: currentRecordFieldNewPosition,
|
||||
};
|
||||
|
||||
newRecordFields[targetRecordFieldNewPosition] = {
|
||||
...newRecordFields[targetRecordFieldNewPosition],
|
||||
updateRecordField(targetRecordField.fieldMetadataItemId, {
|
||||
position: targetRecordFieldNewPosition,
|
||||
};
|
||||
});
|
||||
|
||||
set(currentRecordFieldsCallbackState, newRecordFields);
|
||||
updateRecordField(currentRecordField.fieldMetadataItemId, {
|
||||
position: currentRecordFieldNewPosition,
|
||||
});
|
||||
|
||||
saveViewFields([
|
||||
mapRecordFieldToViewField({
|
||||
...targetRecordField,
|
||||
position: targetRecordFieldNewPosition,
|
||||
}),
|
||||
mapRecordFieldToViewField({
|
||||
...currentRecordField,
|
||||
position: currentRecordFieldNewPosition,
|
||||
}),
|
||||
]);
|
||||
|
||||
updateTableColumn(targetRecordField.fieldMetadataItemId, {
|
||||
position: targetRecordFieldNewPosition,
|
||||
});
|
||||
|
||||
updateTableColumn(currentRecordField.fieldMetadataItemId, {
|
||||
position: currentRecordFieldNewPosition,
|
||||
});
|
||||
}
|
||||
},
|
||||
[currentRecordFieldsCallbackState],
|
||||
[
|
||||
currentRecordFieldsCallbackState,
|
||||
updateTableColumn,
|
||||
saveViewFields,
|
||||
updateRecordField,
|
||||
],
|
||||
);
|
||||
|
||||
return { moveRecordField };
|
||||
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
import { currentRecordFieldsComponentState } from '@/object-record/record-field/states/currentRecordFieldsComponentState';
|
||||
import { type RecordField } from '@/object-record/record-field/types/RecordField';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { moveArrayItem } from '~/utils/array/moveArrayItem';
|
||||
|
||||
export const useReorderRecordFields = () => {
|
||||
const currentRecordFieldsCallbackState = useRecoilComponentCallbackState(
|
||||
currentRecordFieldsComponentState,
|
||||
);
|
||||
|
||||
const reorderRecordFields = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
({ fromIndex, toIndex }: { fromIndex: number; toIndex: number }) => {
|
||||
const currentRecordFields = snapshot
|
||||
.getLoadable(currentRecordFieldsCallbackState)
|
||||
.getValue();
|
||||
|
||||
const reorderedRecordFields = moveArrayItem(currentRecordFields, {
|
||||
fromIndex,
|
||||
toIndex,
|
||||
});
|
||||
|
||||
const reorderedRecordFieldsWithNewPosition =
|
||||
reorderedRecordFields.map<RecordField>((recordField, index) => ({
|
||||
...recordField,
|
||||
position: index,
|
||||
}));
|
||||
|
||||
set(
|
||||
currentRecordFieldsCallbackState,
|
||||
reorderedRecordFieldsWithNewPosition,
|
||||
);
|
||||
},
|
||||
[currentRecordFieldsCallbackState],
|
||||
);
|
||||
|
||||
return { reorderRecordFields };
|
||||
};
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
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 { 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';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
export const useReorderVisibleRecordFields = (recordTableId: string) => {
|
||||
const visibleRecordFieldsCallbackState = useRecoilComponentCallbackState(
|
||||
visibleAndReadableRecordFieldsComponentSelector,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const currentRecordFieldsCallbackState = useRecoilComponentCallbackState(
|
||||
currentRecordFieldsComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const { updateRecordField } = useUpdateRecordField(recordTableId);
|
||||
|
||||
const reorderVisibleRecordFields = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
({ fromIndex, toIndex }: { fromIndex: number; toIndex: number }) => {
|
||||
const visibleRecordFields = snapshot
|
||||
.getLoadable(visibleRecordFieldsCallbackState)
|
||||
.getValue();
|
||||
|
||||
const currentRecordFields = snapshot
|
||||
.getLoadable(currentRecordFieldsCallbackState)
|
||||
.getValue();
|
||||
|
||||
const idOfRecordToMove = visibleRecordFields[fromIndex].id;
|
||||
const idOfTargetRecord = visibleRecordFields[toIndex].id;
|
||||
|
||||
const recordToMove = visibleRecordFields[fromIndex];
|
||||
|
||||
const newPositionOfTargetRecord =
|
||||
computeNewPositionOfRecordWithPosition({
|
||||
arrayOfRecordsWithPosition: currentRecordFields,
|
||||
idOfItemToMove: idOfRecordToMove,
|
||||
idOfTargetItem: idOfTargetRecord,
|
||||
});
|
||||
|
||||
updateRecordField(recordToMove.fieldMetadataItemId, {
|
||||
position: newPositionOfTargetRecord,
|
||||
});
|
||||
|
||||
const updatedRecordField: RecordField = {
|
||||
...recordToMove,
|
||||
position: newPositionOfTargetRecord,
|
||||
};
|
||||
|
||||
return updatedRecordField;
|
||||
},
|
||||
[
|
||||
currentRecordFieldsCallbackState,
|
||||
visibleRecordFieldsCallbackState,
|
||||
updateRecordField,
|
||||
],
|
||||
);
|
||||
|
||||
return { reorderVisibleRecordFields };
|
||||
};
|
||||
+2
-1
@@ -4,9 +4,10 @@ import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-
|
||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
export const useUpdateRecordField = () => {
|
||||
export const useUpdateRecordField = (recordTableId?: string) => {
|
||||
const currentRecordFieldsCallbackState = useRecoilComponentCallbackState(
|
||||
currentRecordFieldsComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const updateRecordField = useRecoilCallback(
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
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<ColumnDefinition<any>, '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,
|
||||
};
|
||||
};
|
||||
+2
-1
@@ -4,9 +4,10 @@ import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-
|
||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
export const useUpsertRecordField = () => {
|
||||
export const useUpsertRecordField = (recordTableId?: string) => {
|
||||
const currentRecordFieldsCallbackState = useRecoilComponentCallbackState(
|
||||
currentRecordFieldsComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const upsertRecordField = useRecoilCallback(
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
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'),
|
||||
);
|
||||
},
|
||||
});
|
||||
+1
@@ -117,6 +117,7 @@ export const useLoadRecordIndexStates = () => {
|
||||
const existingRecordIndexFieldDefinitions = snapshot
|
||||
.getLoadable(recordIndexFieldDefinitionsState)
|
||||
.getValue();
|
||||
|
||||
if (
|
||||
!isDeeplyEqual(
|
||||
existingRecordIndexFieldDefinitions,
|
||||
|
||||
-4
@@ -1,5 +1,4 @@
|
||||
import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
import { useSaveColumnsToView } from '@/object-record/record-table/hooks/useSaveColumnsToView';
|
||||
import { useSetTableColumns } from '@/object-record/record-table/hooks/useSetTableColumns';
|
||||
import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||
|
||||
@@ -7,7 +6,6 @@ import { type ColumnDefinition } from '@/object-record/record-table/types/Column
|
||||
// but for now it allows to have a cleaner API globally.
|
||||
// TODO: should be solved with new RecordField abstraction
|
||||
export const useHandleColumnsChange = () => {
|
||||
const { saveColumnsToView } = useSaveColumnsToView();
|
||||
const { setTableColumns } = useSetTableColumns();
|
||||
|
||||
const handleColumnsChange = async ({
|
||||
@@ -20,8 +18,6 @@ export const useHandleColumnsChange = () => {
|
||||
objectMetadataId: string;
|
||||
}) => {
|
||||
setTableColumns(columns, recordTableId, objectMetadataId);
|
||||
|
||||
await saveColumnsToView(columns);
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
+55
-113
@@ -2,50 +2,44 @@ 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 { useMoveViewColumns } from '@/views/hooks/useMoveViewColumns';
|
||||
|
||||
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 { availableTableColumnsComponentState } from '@/object-record/record-table/states/availableTableColumnsComponentState';
|
||||
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
||||
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 { 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;
|
||||
objectMetadataId: string;
|
||||
};
|
||||
|
||||
export const useTableColumns = ({
|
||||
objectMetadataId,
|
||||
recordTableId,
|
||||
}: useRecordTableProps) => {
|
||||
const availableTableColumns = useRecoilComponentValue(
|
||||
availableTableColumnsComponentState,
|
||||
export const useTableColumns = ({ recordTableId }: useRecordTableProps) => {
|
||||
const currentRecordFields = useRecoilComponentValue(
|
||||
currentRecordFieldsComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const tableColumns = useRecoilComponentValue(
|
||||
tableColumnsComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
const visibleTableColumns = useRecoilComponentValue(
|
||||
visibleTableColumnsComponentSelector,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const { handleColumnMove } = useMoveViewColumns();
|
||||
|
||||
const { unfocusRecordTableCell } = useUnfocusRecordTableCell(recordTableId);
|
||||
|
||||
const { handleColumnsChange } = useHandleColumnsChange();
|
||||
|
||||
const { updateRecordField } = useUpdateRecordField();
|
||||
const { upsertRecordField } = useUpsertRecordField();
|
||||
const { updateRecordField } = useUpdateRecordField(recordTableId);
|
||||
const { upsertRecordField } = useUpsertRecordField(recordTableId);
|
||||
|
||||
const { saveViewFields } = useSaveCurrentViewFields();
|
||||
|
||||
const { updateTableColumn } = useUpdateTableColumn(recordTableId);
|
||||
const { createTableColumn } = useCreateTableColumn(recordTableId);
|
||||
|
||||
const handleColumnVisibilityChange = useCallback(
|
||||
async (
|
||||
@@ -54,74 +48,64 @@ export const useTableColumns = ({
|
||||
'fieldMetadataId' | 'isVisible'
|
||||
>,
|
||||
) => {
|
||||
const shouldShowColumn = !visibleTableColumns.some(
|
||||
(visibleColumn) =>
|
||||
visibleColumn.fieldMetadataId === viewField.fieldMetadataId,
|
||||
const lastPosition = currentRecordFields.toSorted(
|
||||
sortByProperty('position', 'desc'),
|
||||
)[0].position;
|
||||
|
||||
const shouldShowFieldMetadataItem = viewField.isVisible === true;
|
||||
const corresponingRecordField = currentRecordFields.find(
|
||||
(recordFieldToFind) =>
|
||||
recordFieldToFind.fieldMetadataItemId === viewField.fieldMetadataId,
|
||||
);
|
||||
|
||||
const tableColumnPositions = [...tableColumns]
|
||||
.sort((a, b) => b.position - a.position)
|
||||
.map((column) => column.position);
|
||||
const noExistingRecordField = !isDefined(corresponingRecordField);
|
||||
|
||||
const lastPosition = tableColumnPositions[0] ?? 0;
|
||||
|
||||
if (shouldShowColumn) {
|
||||
const newColumn = availableTableColumns.find(
|
||||
(availableTableColumn) =>
|
||||
availableTableColumn.fieldMetadataId === viewField.fieldMetadataId,
|
||||
);
|
||||
|
||||
if (!newColumn) return;
|
||||
|
||||
const nextColumns = [
|
||||
...tableColumns,
|
||||
{ ...newColumn, isVisible: true, position: lastPosition + 1 },
|
||||
];
|
||||
|
||||
upsertRecordField({
|
||||
if (noExistingRecordField) {
|
||||
const recordFieldToUpsert: RecordField = {
|
||||
id: v4(),
|
||||
fieldMetadataItemId: viewField.fieldMetadataId,
|
||||
size: 100,
|
||||
isVisible: true,
|
||||
isVisible: shouldShowFieldMetadataItem,
|
||||
position: lastPosition + 1,
|
||||
};
|
||||
|
||||
upsertRecordField(recordFieldToUpsert);
|
||||
|
||||
createTableColumn(viewField.fieldMetadataId, {
|
||||
position: lastPosition + 1,
|
||||
size: 100,
|
||||
isVisible: shouldShowFieldMetadataItem,
|
||||
});
|
||||
|
||||
await handleColumnsChange({
|
||||
columns: nextColumns,
|
||||
objectMetadataId,
|
||||
recordTableId,
|
||||
});
|
||||
saveViewFields([mapRecordFieldToViewField(recordFieldToUpsert)]);
|
||||
} else {
|
||||
const nextColumns = visibleTableColumns.map((previousColumn) =>
|
||||
previousColumn.fieldMetadataId === viewField.fieldMetadataId
|
||||
? { ...previousColumn, isVisible: !viewField.isVisible }
|
||||
: previousColumn,
|
||||
);
|
||||
|
||||
updateRecordField(viewField.fieldMetadataId, {
|
||||
isVisible: !viewField.isVisible,
|
||||
isVisible: shouldShowFieldMetadataItem,
|
||||
});
|
||||
|
||||
await handleColumnsChange({
|
||||
columns: nextColumns,
|
||||
objectMetadataId,
|
||||
recordTableId,
|
||||
const updatedRecordField: RecordField = {
|
||||
...corresponingRecordField,
|
||||
isVisible: shouldShowFieldMetadataItem,
|
||||
};
|
||||
|
||||
updateTableColumn(viewField.fieldMetadataId, {
|
||||
isVisible: shouldShowFieldMetadataItem,
|
||||
});
|
||||
|
||||
saveViewFields([mapRecordFieldToViewField(updatedRecordField)]);
|
||||
}
|
||||
},
|
||||
[
|
||||
updateTableColumn,
|
||||
saveViewFields,
|
||||
currentRecordFields,
|
||||
upsertRecordField,
|
||||
tableColumns,
|
||||
availableTableColumns,
|
||||
handleColumnsChange,
|
||||
visibleTableColumns,
|
||||
objectMetadataId,
|
||||
recordTableId,
|
||||
updateRecordField,
|
||||
createTableColumn,
|
||||
],
|
||||
);
|
||||
|
||||
const { moveRecordField } = useMoveRecordField();
|
||||
const { moveRecordField } = useMoveRecordField(recordTableId);
|
||||
|
||||
const handleMoveTableColumn = useCallback(
|
||||
async (
|
||||
@@ -130,59 +114,17 @@ export const useTableColumns = ({
|
||||
) => {
|
||||
unfocusRecordTableCell();
|
||||
|
||||
const currentColumnArrayIndex = visibleTableColumns.findIndex(
|
||||
(visibleColumn) =>
|
||||
visibleColumn.fieldMetadataId === column.fieldMetadataId,
|
||||
);
|
||||
|
||||
const columns = handleColumnMove(
|
||||
direction,
|
||||
currentColumnArrayIndex,
|
||||
visibleTableColumns,
|
||||
);
|
||||
|
||||
moveRecordField({
|
||||
direction: direction === 'left' ? 'before' : 'after',
|
||||
fieldMetadataItemIdToMove: column.fieldMetadataId,
|
||||
});
|
||||
|
||||
await handleColumnsChange({
|
||||
columns,
|
||||
objectMetadataId,
|
||||
recordTableId,
|
||||
});
|
||||
},
|
||||
[
|
||||
unfocusRecordTableCell,
|
||||
visibleTableColumns,
|
||||
handleColumnMove,
|
||||
handleColumnsChange,
|
||||
objectMetadataId,
|
||||
recordTableId,
|
||||
moveRecordField,
|
||||
],
|
||||
);
|
||||
|
||||
const handleColumnReorder = useCallback(
|
||||
async (columns: ColumnDefinition<FieldMetadata>[]) => {
|
||||
const updatedColumns = columns.map((column, index) => ({
|
||||
...column,
|
||||
position: index,
|
||||
}));
|
||||
|
||||
await handleColumnsChange({
|
||||
columns: updatedColumns,
|
||||
objectMetadataId,
|
||||
recordTableId,
|
||||
});
|
||||
},
|
||||
[handleColumnsChange, objectMetadataId, recordTableId],
|
||||
[unfocusRecordTableCell, moveRecordField],
|
||||
);
|
||||
|
||||
return {
|
||||
handleColumnVisibilityChange,
|
||||
handleMoveTableColumn,
|
||||
handleColumnReorder,
|
||||
handleColumnsChange,
|
||||
};
|
||||
};
|
||||
|
||||
+5
-2
@@ -57,7 +57,7 @@ export const RecordTableColumnHeadDropdownMenu = ({
|
||||
const { recordTableId } = useRecordTableContextOrThrow();
|
||||
|
||||
const { handleColumnVisibilityChange, handleMoveTableColumn } =
|
||||
useTableColumns({ objectMetadataId, recordTableId });
|
||||
useTableColumns({ recordTableId });
|
||||
|
||||
const dropdownId = column.fieldMetadataId + '-header';
|
||||
|
||||
@@ -86,7 +86,10 @@ export const RecordTableColumnHeadDropdownMenu = ({
|
||||
|
||||
const handleColumnVisibility = () => {
|
||||
closeDropdownAndToggleScroll();
|
||||
handleColumnVisibilityChange(column);
|
||||
handleColumnVisibilityChange({
|
||||
...column,
|
||||
isVisible: false,
|
||||
});
|
||||
};
|
||||
|
||||
const handleToggleColumnSort = useHandleToggleColumnSort({
|
||||
|
||||
-1
@@ -141,7 +141,6 @@ export const RecordTableHeaderCell = ({
|
||||
>(null);
|
||||
|
||||
const { handleColumnsChange } = useTableColumns({
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
recordTableId,
|
||||
});
|
||||
|
||||
|
||||
+1
-2
@@ -31,14 +31,13 @@ export const RecordTableHeaderPlusButtonContent = () => {
|
||||
|
||||
const { getIcon } = useIcons();
|
||||
const { handleColumnVisibilityChange } = useTableColumns({
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
recordTableId,
|
||||
});
|
||||
|
||||
const handleAddColumn = useCallback(
|
||||
(column: ColumnDefinition<FieldMetadata>) => {
|
||||
closeDropdown();
|
||||
handleColumnVisibilityChange(column);
|
||||
handleColumnVisibilityChange({ ...column, isVisible: true });
|
||||
},
|
||||
[handleColumnVisibilityChange, closeDropdown],
|
||||
);
|
||||
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
computeNewPositionOfRecordWithPosition,
|
||||
type RecordWithPosition,
|
||||
} from '@/object-record/utils/computeNewPositionOfRecordWithPosition';
|
||||
|
||||
const mockRecordsWithPosition: RecordWithPosition[] = [
|
||||
{
|
||||
id: 'A',
|
||||
position: 0,
|
||||
},
|
||||
{
|
||||
id: 'B',
|
||||
position: 1,
|
||||
},
|
||||
{
|
||||
id: 'C',
|
||||
position: 2,
|
||||
},
|
||||
{
|
||||
id: 'D',
|
||||
position: 3,
|
||||
},
|
||||
];
|
||||
|
||||
describe('computeNewPositionOfRecordWithPosition', () => {
|
||||
it('should compute first position', () => {
|
||||
const newPosition = computeNewPositionOfRecordWithPosition({
|
||||
arrayOfRecordsWithPosition: mockRecordsWithPosition,
|
||||
idOfItemToMove: 'B',
|
||||
idOfTargetItem: 'A',
|
||||
});
|
||||
|
||||
expect(newPosition).toEqual(-1);
|
||||
});
|
||||
|
||||
it('should compute last position', () => {
|
||||
const newPosition = computeNewPositionOfRecordWithPosition({
|
||||
arrayOfRecordsWithPosition: mockRecordsWithPosition,
|
||||
idOfItemToMove: 'B',
|
||||
idOfTargetItem: 'D',
|
||||
});
|
||||
|
||||
expect(newPosition).toEqual(4);
|
||||
});
|
||||
|
||||
it('should compute intermediary position after target item', () => {
|
||||
const newPosition = computeNewPositionOfRecordWithPosition({
|
||||
arrayOfRecordsWithPosition: mockRecordsWithPosition,
|
||||
idOfItemToMove: 'A',
|
||||
idOfTargetItem: 'B',
|
||||
});
|
||||
|
||||
expect(newPosition).toEqual(1.5);
|
||||
});
|
||||
|
||||
it('should compute intermediary position before target item', () => {
|
||||
const newPosition = computeNewPositionOfRecordWithPosition({
|
||||
arrayOfRecordsWithPosition: mockRecordsWithPosition,
|
||||
idOfItemToMove: 'A',
|
||||
idOfTargetItem: 'C',
|
||||
});
|
||||
|
||||
expect(newPosition).toEqual(2.5);
|
||||
});
|
||||
});
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { sortByProperty } from '~/utils/array/sortByProperty';
|
||||
|
||||
export type RecordWithPosition = {
|
||||
id: string;
|
||||
position: number;
|
||||
};
|
||||
|
||||
export const computeNewPositionOfRecordWithPosition = ({
|
||||
arrayOfRecordsWithPosition,
|
||||
idOfItemToMove,
|
||||
idOfTargetItem,
|
||||
}: {
|
||||
arrayOfRecordsWithPosition: RecordWithPosition[];
|
||||
idOfItemToMove: string;
|
||||
idOfTargetItem: string;
|
||||
}) => {
|
||||
const itemToMove = arrayOfRecordsWithPosition.find(
|
||||
(recordToFind) => recordToFind.id === idOfItemToMove,
|
||||
);
|
||||
|
||||
const targetItem = arrayOfRecordsWithPosition.find(
|
||||
(recordToFind) => recordToFind.id === idOfTargetItem,
|
||||
);
|
||||
|
||||
if (!isDefined(itemToMove)) {
|
||||
throw new Error(`Cannot find item to move for id : ${idOfItemToMove}`);
|
||||
}
|
||||
|
||||
if (!isDefined(targetItem)) {
|
||||
throw new Error(`Cannot find item to move for id : ${idOfTargetItem}`);
|
||||
}
|
||||
|
||||
if (itemToMove.id === targetItem.id) {
|
||||
return itemToMove.position;
|
||||
}
|
||||
|
||||
const targetPosition = targetItem.position;
|
||||
|
||||
const sortedRecordsByAscendingPosition = arrayOfRecordsWithPosition.toSorted(
|
||||
sortByProperty('position'),
|
||||
);
|
||||
|
||||
const indexOfItemToMove = sortedRecordsByAscendingPosition.findIndex(
|
||||
(recordToFind) => recordToFind.id === idOfItemToMove,
|
||||
);
|
||||
const indexOfTargetItem = sortedRecordsByAscendingPosition.findIndex(
|
||||
(recordToFind) => recordToFind.id === idOfTargetItem,
|
||||
);
|
||||
|
||||
const lastIndex = sortedRecordsByAscendingPosition.length - 1;
|
||||
|
||||
const shouldGoToFirstPosition =
|
||||
indexOfItemToMove > 0 && indexOfTargetItem === 0;
|
||||
|
||||
const shouldGoToLastPosition =
|
||||
indexOfItemToMove < lastIndex && indexOfTargetItem === lastIndex;
|
||||
|
||||
if (shouldGoToFirstPosition) {
|
||||
return targetPosition - 1;
|
||||
} else if (shouldGoToLastPosition) {
|
||||
return targetPosition + 1;
|
||||
} else {
|
||||
const shouldGoAfterTargetItem = indexOfItemToMove < indexOfTargetItem;
|
||||
|
||||
if (shouldGoAfterTargetItem) {
|
||||
const itemAfterTargetItem =
|
||||
sortedRecordsByAscendingPosition[indexOfTargetItem + 1];
|
||||
|
||||
const intermediaryPosition =
|
||||
targetItem.position +
|
||||
(itemAfterTargetItem.position - targetItem.position) / 2;
|
||||
|
||||
return intermediaryPosition;
|
||||
} else {
|
||||
const itemBeforeTargetItem =
|
||||
sortedRecordsByAscendingPosition[indexOfTargetItem - 1];
|
||||
|
||||
const intermediaryPosition =
|
||||
targetItem.position -
|
||||
(targetItem.position - itemBeforeTargetItem.position) / 2;
|
||||
|
||||
return intermediaryPosition;
|
||||
}
|
||||
}
|
||||
};
|
||||
+29
-60
@@ -1,48 +1,36 @@
|
||||
import { type DropResult, type ResponderProvided } from '@hello-pangea/dnd';
|
||||
|
||||
import { useActiveFieldMetadataItems } from '@/object-metadata/hooks/useActiveFieldMetadataItems';
|
||||
import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard';
|
||||
import { useObjectOptionsForTable } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForTable';
|
||||
import { ObjectOptionsDropdownContext } from '@/object-record/object-options-dropdown/states/contexts/ObjectOptionsDropdownContext';
|
||||
import { currentRecordFieldsComponentState } from '@/object-record/record-field/states/currentRecordFieldsComponentState';
|
||||
import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem';
|
||||
import { DraggableList } from '@/ui/layout/draggable-list/components/DraggableList';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { useContext } from 'react';
|
||||
import { IconEye, useIcons } from 'twenty-ui/display';
|
||||
import { MenuItemDraggable } from 'twenty-ui/navigation';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
|
||||
export const ViewFieldsHiddenDropdownSection = () => {
|
||||
const { viewType, objectMetadataItem, recordIndexId } = useContext(
|
||||
ObjectOptionsDropdownContext,
|
||||
);
|
||||
|
||||
const { handleReorderColumns, handleColumnVisibilityChange } =
|
||||
useObjectOptionsForTable(recordIndexId, objectMetadataItem.id);
|
||||
const { handleColumnVisibilityChange } = useObjectOptionsForTable(
|
||||
recordIndexId,
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
|
||||
const { handleReorderBoardFields, handleBoardFieldVisibilityChange } =
|
||||
useObjectOptionsForBoard({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
recordBoardId: recordIndexId,
|
||||
viewBarId: recordIndexId,
|
||||
});
|
||||
|
||||
const handleReorderFields =
|
||||
viewType === ViewType.Kanban
|
||||
? handleReorderBoardFields
|
||||
: handleReorderColumns;
|
||||
const { handleBoardFieldVisibilityChange } = useObjectOptionsForBoard({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
recordBoardId: recordIndexId,
|
||||
viewBarId: recordIndexId,
|
||||
});
|
||||
|
||||
const handleChangeFieldVisibility =
|
||||
viewType === ViewType.Kanban
|
||||
? handleBoardFieldVisibilityChange
|
||||
: handleColumnVisibilityChange;
|
||||
|
||||
const handleDragEnd = (result: DropResult, provided: ResponderProvided) => {
|
||||
handleReorderFields(result, provided);
|
||||
};
|
||||
|
||||
const currentRecordFields = useRecoilComponentValue(
|
||||
currentRecordFieldsComponentState,
|
||||
);
|
||||
@@ -67,45 +55,26 @@ export const ViewFieldsHiddenDropdownSection = () => {
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuItemsContainer>
|
||||
{availableFieldMetadataItemsToShow.length > 0 && (
|
||||
<DraggableList
|
||||
onDragEnd={handleDragEnd}
|
||||
draggableItems={
|
||||
<>
|
||||
{availableFieldMetadataItemsToShow.map(
|
||||
(fieldMetadataItem, index) => {
|
||||
const fieldIndex = index;
|
||||
|
||||
return (
|
||||
<DraggableItem
|
||||
key={fieldMetadataItem.id}
|
||||
draggableId={fieldMetadataItem.id}
|
||||
index={fieldIndex + 1}
|
||||
itemComponent={
|
||||
<MenuItemDraggable
|
||||
key={fieldMetadataItem.id}
|
||||
LeftIcon={getIcon(fieldMetadataItem.icon)}
|
||||
iconButtons={[
|
||||
{
|
||||
Icon: IconEye,
|
||||
onClick: () =>
|
||||
handleChangeFieldVisibility({
|
||||
fieldMetadataId: fieldMetadataItem.id,
|
||||
isVisible: false,
|
||||
}),
|
||||
},
|
||||
]}
|
||||
text={fieldMetadataItem.label}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
{availableFieldMetadataItemsToShow.length > 0 &&
|
||||
availableFieldMetadataItemsToShow.map((fieldMetadataItem) => {
|
||||
return (
|
||||
<MenuItem
|
||||
key={fieldMetadataItem.id}
|
||||
LeftIcon={getIcon(fieldMetadataItem.icon)}
|
||||
iconButtons={[
|
||||
{
|
||||
Icon: IconEye,
|
||||
onClick: () =>
|
||||
handleChangeFieldVisibility({
|
||||
fieldMetadataId: fieldMetadataItem.id,
|
||||
isVisible: true,
|
||||
}),
|
||||
},
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
]}
|
||||
text={fieldMetadataItem.label}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
|
||||
+28
-29
@@ -5,9 +5,11 @@ import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/get
|
||||
import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard';
|
||||
import { useObjectOptionsForTable } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForTable';
|
||||
import { ObjectOptionsDropdownContext } from '@/object-record/object-options-dropdown/states/contexts/ObjectOptionsDropdownContext';
|
||||
import { visibleAndReadableRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleAndReadableRecordFieldsComponentSelector';
|
||||
import { DraggableItem } from '@/ui/layout/draggable-list/components/DraggableItem';
|
||||
import { DraggableList } from '@/ui/layout/draggable-list/components/DraggableList';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { useContext } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
@@ -20,24 +22,15 @@ export const ViewFieldsVisibleDropdownSection = () => {
|
||||
ObjectOptionsDropdownContext,
|
||||
);
|
||||
|
||||
const {
|
||||
handleReorderColumns,
|
||||
handleColumnVisibilityChange,
|
||||
visibleTableColumns,
|
||||
} = useObjectOptionsForTable(recordIndexId, objectMetadataItem.id);
|
||||
const { handleReorderColumns, handleColumnVisibilityChange } =
|
||||
useObjectOptionsForTable(recordIndexId, objectMetadataItem.id);
|
||||
|
||||
const {
|
||||
handleReorderBoardFields,
|
||||
handleBoardFieldVisibilityChange,
|
||||
visibleBoardFields,
|
||||
} = useObjectOptionsForBoard({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
recordBoardId: recordIndexId,
|
||||
viewBarId: recordIndexId,
|
||||
});
|
||||
|
||||
const visibleRecordFields =
|
||||
viewType === ViewType.Kanban ? visibleBoardFields : visibleTableColumns;
|
||||
const { handleReorderBoardFields, handleBoardFieldVisibilityChange } =
|
||||
useObjectOptionsForBoard({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
recordBoardId: recordIndexId,
|
||||
viewBarId: recordIndexId,
|
||||
});
|
||||
|
||||
const { getFieldMetadataItemById } = useGetFieldMetadataItemById();
|
||||
|
||||
@@ -60,19 +53,23 @@ export const ViewFieldsVisibleDropdownSection = () => {
|
||||
const fieldMetadataItemLabelIdentifier =
|
||||
getLabelIdentifierFieldMetadataItem(objectMetadataItem);
|
||||
|
||||
const visibleRecordFields = useRecoilComponentValue(
|
||||
visibleAndReadableRecordFieldsComponentSelector,
|
||||
);
|
||||
|
||||
const nonDraggableRecordField = visibleRecordFields.find(
|
||||
(recordFieldToFilter) =>
|
||||
recordFieldToFilter.fieldMetadataId ===
|
||||
recordFieldToFilter.fieldMetadataItemId ===
|
||||
fieldMetadataItemLabelIdentifier?.id,
|
||||
);
|
||||
|
||||
const draggableRecordFields = visibleRecordFields
|
||||
.filter(
|
||||
(recordFieldToFilter) =>
|
||||
recordFieldToFilter.fieldMetadataId !==
|
||||
nonDraggableRecordField?.fieldMetadataId,
|
||||
nonDraggableRecordField?.fieldMetadataItemId !==
|
||||
recordFieldToFilter.fieldMetadataItemId,
|
||||
)
|
||||
.sort(sortByProperty('position'));
|
||||
.toSorted(sortByProperty('position'));
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -97,26 +94,28 @@ export const ViewFieldsVisibleDropdownSection = () => {
|
||||
(isDefined(fieldMetadataItemLabelIdentifier) ? 1 : 0);
|
||||
|
||||
const fieldMetadataItem = getFieldMetadataItemById(
|
||||
recordField.fieldMetadataId,
|
||||
recordField.fieldMetadataItemId,
|
||||
);
|
||||
|
||||
return (
|
||||
<DraggableItem
|
||||
key={recordField.fieldMetadataId}
|
||||
draggableId={recordField.fieldMetadataId}
|
||||
key={recordField.fieldMetadataItemId}
|
||||
draggableId={recordField.fieldMetadataItemId}
|
||||
index={fieldIndex + 1}
|
||||
itemComponent={
|
||||
<MenuItemDraggable
|
||||
key={recordField.fieldMetadataId}
|
||||
key={recordField.fieldMetadataItemId}
|
||||
LeftIcon={getIcon(fieldMetadataItem.icon)}
|
||||
iconButtons={[
|
||||
{
|
||||
Icon: IconEyeOff,
|
||||
onClick: () =>
|
||||
onClick: () => {
|
||||
handleChangeFieldVisibility({
|
||||
fieldMetadataId: recordField.fieldMetadataId,
|
||||
isVisible: recordField.isVisible,
|
||||
}),
|
||||
fieldMetadataId:
|
||||
recordField.fieldMetadataItemId,
|
||||
isVisible: false,
|
||||
});
|
||||
},
|
||||
},
|
||||
]}
|
||||
text={fieldMetadataItem.label}
|
||||
|
||||
+5
-2
@@ -38,7 +38,10 @@ export const usePersistViewFieldRecords = () => {
|
||||
const apolloCoreClient = useApolloCoreClient();
|
||||
|
||||
const createViewFieldRecords = useCallback(
|
||||
(viewFieldsToCreate: ViewField[], view: GraphQLView) => {
|
||||
(
|
||||
viewFieldsToCreate: Omit<ViewField, 'definition'>[],
|
||||
view: GraphQLView,
|
||||
) => {
|
||||
if (!viewFieldsToCreate.length) return;
|
||||
return Promise.all(
|
||||
viewFieldsToCreate.map((viewField) =>
|
||||
@@ -80,7 +83,7 @@ export const usePersistViewFieldRecords = () => {
|
||||
);
|
||||
|
||||
const updateViewFieldRecords = useCallback(
|
||||
(viewFieldsToUpdate: ViewField[]) => {
|
||||
(viewFieldsToUpdate: Omit<ViewField, 'definition'>[]) => {
|
||||
if (!viewFieldsToUpdate.length) return;
|
||||
|
||||
return Promise.all(
|
||||
|
||||
@@ -22,7 +22,7 @@ export const useSaveCurrentViewFields = () => {
|
||||
|
||||
const saveViewFields = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
async (viewFieldsToSave: ViewField[]) => {
|
||||
async (viewFieldsToSave: Omit<ViewField, 'definition'>[]) => {
|
||||
const currentViewId = snapshot
|
||||
.getLoadable(currentViewIdCallbackState)
|
||||
.getValue();
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { type RecordField } from '@/object-record/record-field/types/RecordField';
|
||||
import { type ViewField } from '@/views/types/ViewField';
|
||||
|
||||
export const mapRecordFieldToViewField = (recordField: RecordField) => {
|
||||
const viewField: Omit<ViewField, 'definition'> = {
|
||||
id: recordField.id,
|
||||
fieldMetadataId: recordField.fieldMetadataItemId,
|
||||
isVisible: recordField.isVisible,
|
||||
position: recordField.position,
|
||||
size: recordField.size,
|
||||
aggregateOperation: recordField.aggregateOperation,
|
||||
__typename: 'ViewField',
|
||||
};
|
||||
|
||||
return viewField;
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
export const findById = <T extends { id: string }>(idToMatch: string) => {
|
||||
return (itemToFind: T) => {
|
||||
return itemToFind.id === idToMatch
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export const findByProperty = <T, K extends keyof T>(property: K, valueToMatch: T[K]) => {
|
||||
return (itemToFind: T) => {
|
||||
return itemToFind[property] === valueToMatch
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@
|
||||
* |___/
|
||||
*/
|
||||
|
||||
export { findById } from './array/findById';
|
||||
export { findByProperty } from './array/findByProperty';
|
||||
export { assertUnreachable } from './assertUnreachable';
|
||||
export { deepMerge } from './deepMerge';
|
||||
export { extractAndSanitizeObjectStringFields } from './extractAndSanitizeObjectStringFields';
|
||||
|
||||
@@ -46,8 +46,8 @@
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"assets",
|
||||
"accessibility",
|
||||
"assets",
|
||||
"components",
|
||||
"display",
|
||||
"feedback",
|
||||
@@ -170,4 +170,4 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user