Refactor table columns states and utils (#14048)
This PR refactors table columns state, hooks and utils, it removes them and replaces them by record fields.
This commit is contained in:
@@ -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,
|
||||
});
|
||||
|
||||
+38
@@ -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,
|
||||
};
|
||||
},
|
||||
});
|
||||
+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 flattenedFieldMetadataItemsSelector = selector<
|
||||
FieldMetadataItem[]
|
||||
>({
|
||||
key: 'flattenedFieldMetadataItemsSelector',
|
||||
get: ({ get }) => {
|
||||
const objectMetadataItems = get(objectMetadataItemsState);
|
||||
|
||||
const flattenedFieldMetadataItems = objectMetadataItems.flatMap(
|
||||
(objectMetadataItem) => objectMetadataItem.fields,
|
||||
);
|
||||
|
||||
return flattenedFieldMetadataItems;
|
||||
},
|
||||
});
|
||||
+66
@@ -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,
|
||||
};
|
||||
},
|
||||
});
|
||||
+37
@@ -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;
|
||||
},
|
||||
});
|
||||
+31
@@ -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;
|
||||
},
|
||||
});
|
||||
+19
-15
@@ -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 = ({
|
||||
<>
|
||||
<DropdownMenuSectionLabel label={t`Visible fields`} />
|
||||
<DropdownMenuItemsContainer>
|
||||
{visibleColumnsFieldMetadataItems.map(
|
||||
{visibleFieldMetadataItems.map(
|
||||
(visibleFieldMetadataItem, index) => (
|
||||
<SelectableListItem
|
||||
itemId={visibleFieldMetadataItem.id}
|
||||
@@ -174,7 +178,7 @@ export const AdvancedFilterFieldSelectMenu = ({
|
||||
{shouldShowSeparator && <DropdownMenuSeparator />}
|
||||
{shouldShowHiddenFields && (
|
||||
<>
|
||||
{visibleColumnsFieldMetadataItems.length > 0 && (
|
||||
{visibleFieldMetadataItems.length > 0 && (
|
||||
<DropdownMenuSectionLabel label={t`Hidden fields`} />
|
||||
)}
|
||||
<DropdownMenuItemsContainer>
|
||||
|
||||
+26
-11
@@ -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 (
|
||||
<HookMockWrapper>
|
||||
<ObjectNamePluralSetter>
|
||||
<ViewComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'instanceId' }}
|
||||
>
|
||||
<RecordTableComponentInstance recordTableId={recordTableId}>
|
||||
<RecordGroupContext.Provider value={{ recordGroupId: 'default' }}>
|
||||
{children}
|
||||
</RecordGroupContext.Provider>
|
||||
</RecordTableComponentInstance>
|
||||
</ViewComponentInstanceContext.Provider>
|
||||
</ObjectNamePluralSetter>
|
||||
<RecordTableContextProvider
|
||||
objectNameSingular={objectNameSingular}
|
||||
recordTableId={recordTableId}
|
||||
viewBarId="instanceId"
|
||||
>
|
||||
<ObjectNamePluralSetter>
|
||||
<ViewComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'instanceId' }}
|
||||
>
|
||||
<RecordComponentInstanceContextsWrapper
|
||||
componentInstanceId={recordTableId}
|
||||
>
|
||||
<RecordTableComponentInstance recordTableId={recordTableId}>
|
||||
<RecordGroupContext.Provider
|
||||
value={{ recordGroupId: 'default' }}
|
||||
>
|
||||
{children}
|
||||
</RecordGroupContext.Provider>
|
||||
</RecordTableComponentInstance>
|
||||
</RecordComponentInstanceContextsWrapper>
|
||||
</ViewComponentInstanceContext.Provider>
|
||||
</ObjectNamePluralSetter>
|
||||
</RecordTableContextProvider>
|
||||
</HookMockWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
+9
-8
@@ -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 {
|
||||
|
||||
-116
@@ -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,
|
||||
};
|
||||
};
|
||||
+38
@@ -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,
|
||||
};
|
||||
};
|
||||
+21
-23
@@ -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 = () => {
|
||||
<>
|
||||
<DropdownMenuSectionLabel label={t`Hidden fields`} />
|
||||
<DropdownMenuItemsContainer>
|
||||
{hiddenFieldMetadataItems.map(
|
||||
{hiddenFieldMetadataItemsSorted.map(
|
||||
(hiddenFieldMetadataItem, index) => (
|
||||
<SelectableListItem
|
||||
key={hiddenFieldMetadataItem.id}
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
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 { 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';
|
||||
|
||||
export const useChangeRecordFieldVisibility = (
|
||||
recordFieldComponentInstanceId?: string,
|
||||
) => {
|
||||
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,
|
||||
};
|
||||
};
|
||||
-77
@@ -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<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,
|
||||
};
|
||||
};
|
||||
+3
-17
@@ -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 };
|
||||
|
||||
+2
-2
@@ -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,
|
||||
);
|
||||
|
||||
|
||||
+12
-4
@@ -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],
|
||||
|
||||
-56
@@ -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<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,
|
||||
};
|
||||
};
|
||||
-41
@@ -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'),
|
||||
);
|
||||
},
|
||||
});
|
||||
+39
@@ -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'),
|
||||
);
|
||||
},
|
||||
});
|
||||
+1
-9
@@ -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(
|
||||
|
||||
+16
-158
@@ -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', () => {
|
||||
|
||||
+5
-6
@@ -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]
|
||||
: []),
|
||||
|
||||
+3
-22
@@ -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,
|
||||
|
||||
+15
-10
@@ -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.`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+53
-4
@@ -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 (
|
||||
<RecordTableContextInternalProvider
|
||||
value={{
|
||||
viewBarId,
|
||||
objectMetadataItem,
|
||||
visibleTableColumns,
|
||||
recordTableId,
|
||||
objectNameSingular,
|
||||
objectPermissions,
|
||||
recordFieldByFieldMetadataItemId,
|
||||
labelIdentifierFieldMetadataItem,
|
||||
visibleRecordFields,
|
||||
fieldMetadataItemByFieldMetadataItemId,
|
||||
fieldDefinitionByFieldMetadataItemId,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
+170
-67
@@ -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 (
|
||||
<RecordIndexContextProvider
|
||||
value={{
|
||||
@@ -58,86 +139,100 @@ const meta: Meta = {
|
||||
onIndexRecordsLoaded: () => {},
|
||||
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',
|
||||
}}
|
||||
>
|
||||
<RecordTableContextProvider
|
||||
value={{
|
||||
recordTableId: 'recordTableId',
|
||||
viewBarId: mockPerformance.recordId,
|
||||
objectMetadataItem: mockPerformance.objectMetadataItem as any,
|
||||
visibleTableColumns: mockPerformance.visibleTableColumns as any,
|
||||
objectNameSingular:
|
||||
mockPerformance.objectMetadataItem.nameSingular,
|
||||
objectPermissions: {
|
||||
objectMetadataId: mockPerformance.objectMetadataItem.id,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<RecordTableComponentInstance recordTableId="asd">
|
||||
<RecordTableBodyContextProvider
|
||||
value={{
|
||||
onOpenTableCell: () => {},
|
||||
onMoveFocus: () => {},
|
||||
onCloseTableCell: () => {},
|
||||
onMoveHoverToCurrentCell: () => {},
|
||||
onActionMenuDropdownOpened: () => {},
|
||||
onCellMouseEnter: () => {},
|
||||
}}
|
||||
>
|
||||
<RecordTableRowContextProvider
|
||||
<RecordComponentInstanceContextsWrapper componentInstanceId="recordTableId">
|
||||
<RecordTableContextProvider
|
||||
value={{
|
||||
recordTableId: 'recordTableId',
|
||||
viewBarId: mockPerformance.recordId,
|
||||
// TODO: update performance mocks with new data, and merge with common mocks if possible
|
||||
objectMetadataItem: mockPerformance.objectMetadataItem as any,
|
||||
objectNameSingular:
|
||||
mockPerformance.objectMetadataItem.nameSingular,
|
||||
objectPermissions: {
|
||||
objectMetadataId: mockPerformance.objectMetadataItem.id,
|
||||
},
|
||||
fieldDefinitionByFieldMetadataItemId,
|
||||
fieldMetadataItemByFieldMetadataItemId,
|
||||
labelIdentifierFieldMetadataItem,
|
||||
recordFieldByFieldMetadataItemId,
|
||||
visibleRecordFields,
|
||||
}}
|
||||
>
|
||||
<RecordTableComponentInstance recordTableId={'recordTableId'}>
|
||||
<RecordTableBodyContextProvider
|
||||
value={{
|
||||
objectNameSingular:
|
||||
mockPerformance.entityValue.__typename.toLocaleLowerCase(),
|
||||
recordId: mockPerformance.recordId,
|
||||
rowIndex: 0,
|
||||
pathToShowPage:
|
||||
getBasePathToShowPage({
|
||||
objectNameSingular:
|
||||
mockPerformance.entityValue.__typename.toLocaleLowerCase(),
|
||||
}) + mockPerformance.recordId,
|
||||
isSelected: false,
|
||||
inView: true,
|
||||
onOpenTableCell: () => {},
|
||||
onMoveFocus: () => {},
|
||||
onCloseTableCell: () => {},
|
||||
onMoveHoverToCurrentCell: () => {},
|
||||
onActionMenuDropdownOpened: () => {},
|
||||
onCellMouseEnter: () => {},
|
||||
}}
|
||||
>
|
||||
<RecordTableRowDraggableContextProvider
|
||||
<RecordTableRowContextProvider
|
||||
value={{
|
||||
isDragging: false,
|
||||
dragHandleProps: null,
|
||||
objectNameSingular:
|
||||
mockPerformance.entityValue.__typename.toLocaleLowerCase(),
|
||||
recordId: mockPerformance.recordId,
|
||||
rowIndex: 0,
|
||||
pathToShowPage:
|
||||
getBasePathToShowPage({
|
||||
objectNameSingular:
|
||||
mockPerformance.entityValue.__typename.toLocaleLowerCase(),
|
||||
}) + mockPerformance.recordId,
|
||||
isSelected: false,
|
||||
inView: true,
|
||||
}}
|
||||
>
|
||||
<RecordTableCellContext.Provider
|
||||
<RecordTableRowDraggableContextProvider
|
||||
value={{
|
||||
columnDefinition: mockPerformance.fieldDefinition,
|
||||
cellPosition: { row: 0, column: 0 },
|
||||
isDragging: false,
|
||||
dragHandleProps: null,
|
||||
}}
|
||||
>
|
||||
<FieldContext.Provider
|
||||
<RecordTableCellContext.Provider
|
||||
value={{
|
||||
recordId: mockPerformance.recordId,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: {
|
||||
...mockPerformance.fieldDefinition,
|
||||
cellPosition: { row: 0, column: 0 },
|
||||
recordField: {
|
||||
fieldMetadataItemId:
|
||||
mockPerformance.fieldDefinition.fieldMetadataId,
|
||||
id: 'test',
|
||||
isVisible:
|
||||
mockPerformance.fieldDefinition.isVisible,
|
||||
position: mockPerformance.fieldDefinition.position,
|
||||
size: mockPerformance.fieldDefinition.size,
|
||||
},
|
||||
isRecordFieldReadOnly: false,
|
||||
}}
|
||||
>
|
||||
<RelationFieldValueSetterEffect />
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<Story />
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</FieldContext.Provider>
|
||||
</RecordTableCellContext.Provider>
|
||||
</RecordTableRowDraggableContextProvider>
|
||||
</RecordTableRowContextProvider>
|
||||
</RecordTableBodyContextProvider>
|
||||
</RecordTableComponentInstance>
|
||||
</RecordTableContextProvider>
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
recordId: mockPerformance.recordId,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: mockPerformance.fieldDefinition,
|
||||
isRecordFieldReadOnly: false,
|
||||
}}
|
||||
>
|
||||
<RelationFieldValueSetterEffect />
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<Story />
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</FieldContext.Provider>
|
||||
</RecordTableCellContext.Provider>
|
||||
</RecordTableRowDraggableContextProvider>
|
||||
</RecordTableRowContextProvider>
|
||||
</RecordTableBodyContextProvider>
|
||||
</RecordTableComponentInstance>
|
||||
</RecordTableContextProvider>
|
||||
</RecordComponentInstanceContextsWrapper>
|
||||
</RecordIndexContextProvider>
|
||||
);
|
||||
},
|
||||
@@ -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 },
|
||||
},
|
||||
|
||||
+5
-5
@@ -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,
|
||||
|
||||
+2
-3
@@ -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<FieldMetadata>;
|
||||
recordField: RecordField;
|
||||
cellPosition: TableCellPosition;
|
||||
};
|
||||
|
||||
|
||||
+10
-1
@@ -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<FieldMetadata>[];
|
||||
objectPermissions: ObjectPermission;
|
||||
visibleRecordFields: RecordField[];
|
||||
recordFieldByFieldMetadataItemId: Record<string, RecordField>;
|
||||
labelIdentifierFieldMetadataItem: FieldMetadataItem | undefined;
|
||||
fieldMetadataItemByFieldMetadataItemId: Record<string, FieldMetadataItem>;
|
||||
fieldDefinitionByFieldMetadataItemId: Record<
|
||||
string,
|
||||
ColumnDefinition<FieldMetadata>
|
||||
>;
|
||||
};
|
||||
|
||||
export const [RecordTableContextProvider, useRecordTableContextOrThrow] =
|
||||
|
||||
-26
@@ -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<FieldMetadata>[];
|
||||
recordTableId: string;
|
||||
objectMetadataId: string;
|
||||
}) => {
|
||||
setTableColumns(columns, recordTableId, objectMetadataId);
|
||||
};
|
||||
|
||||
return {
|
||||
handleColumnsChange,
|
||||
};
|
||||
};
|
||||
+31
@@ -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,
|
||||
};
|
||||
};
|
||||
+19
-15
@@ -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) => {
|
||||
|
||||
-22
@@ -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<FieldMetadata>[],
|
||||
),
|
||||
);
|
||||
},
|
||||
[saveViewFields],
|
||||
);
|
||||
|
||||
return { saveColumnsToView };
|
||||
};
|
||||
-43
@@ -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<FieldMetadata>[]) => {
|
||||
const availableTableColumns = getSnapshotValue(
|
||||
snapshot,
|
||||
availableTableColumnsState,
|
||||
);
|
||||
|
||||
if (isDeeplyEqual(availableTableColumns, columns)) {
|
||||
return;
|
||||
}
|
||||
set(availableTableColumnsState, columns);
|
||||
},
|
||||
[availableTableColumnsState],
|
||||
);
|
||||
|
||||
return {
|
||||
setAvailableTableColumns,
|
||||
};
|
||||
};
|
||||
-58
@@ -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<FieldMetadata>[],
|
||||
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 };
|
||||
};
|
||||
-130
@@ -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<FieldMetadata>,
|
||||
'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<FieldMetadata>,
|
||||
) => {
|
||||
unfocusRecordTableCell();
|
||||
|
||||
moveRecordField({
|
||||
direction: direction === 'left' ? 'before' : 'after',
|
||||
fieldMetadataItemIdToMove: column.fieldMetadataId,
|
||||
});
|
||||
},
|
||||
[unfocusRecordTableCell, moveRecordField],
|
||||
);
|
||||
|
||||
return {
|
||||
handleColumnVisibilityChange,
|
||||
handleMoveTableColumn,
|
||||
handleColumnsChange,
|
||||
};
|
||||
};
|
||||
+4
-7
@@ -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 (
|
||||
<tbody>
|
||||
@@ -41,8 +38,8 @@ export const RecordTableBodyLoading = () => {
|
||||
>
|
||||
<RecordTableCellGrip />
|
||||
<RecordTableCellCheckbox />
|
||||
{visibleTableColumns.map((column) => (
|
||||
<RecordTableCellLoading key={column.fieldMetadataId} />
|
||||
{visibleRecordFields.map((recordField) => (
|
||||
<RecordTableCellLoading key={recordField.fieldMetadataItemId} />
|
||||
))}
|
||||
</RecordTableTr>
|
||||
</RecordTableRowDraggableContextProvider>
|
||||
|
||||
+19
-12
@@ -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 = ({
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
recordId,
|
||||
fieldDefinition: columnDefinition,
|
||||
fieldDefinition: fieldDefinition,
|
||||
useUpdateRecord: () => [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,
|
||||
|
||||
+10
-5
@@ -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 (
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
recordId,
|
||||
fieldDefinition: columnDefinition,
|
||||
fieldDefinition,
|
||||
useUpdateRecord: () => [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();
|
||||
|
||||
+15
-12
@@ -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}
|
||||
</RecordTableCellFieldContextLabelIdentifier>
|
||||
) : (
|
||||
<RecordTableCellFieldContextGeneric key={instanceId}>
|
||||
<RecordTableCellFieldContextGeneric
|
||||
key={instanceId}
|
||||
recordField={recordField}
|
||||
>
|
||||
{children}
|
||||
</RecordTableCellFieldContextGeneric>
|
||||
)}
|
||||
|
||||
+7
-5
@@ -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 = ({
|
||||
>
|
||||
<RecordTableCellContext.Provider
|
||||
value={{
|
||||
columnDefinition: visibleTableColumns[position.column],
|
||||
recordField: visibleRecordFields[position.column],
|
||||
cellPosition: position,
|
||||
}}
|
||||
>
|
||||
<RecordTableCellFieldContextWrapper>
|
||||
<RecordTableCellFieldContextWrapper
|
||||
recordField={visibleRecordFields[position.column]}
|
||||
>
|
||||
{children}
|
||||
</RecordTableCellFieldContextWrapper>
|
||||
</RecordTableCellContext.Provider>
|
||||
|
||||
+10
-11
@@ -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<FieldMetadata>;
|
||||
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 (
|
||||
<RecordTableCellContext.Provider
|
||||
value={{
|
||||
columnDefinition: column,
|
||||
recordField,
|
||||
cellPosition: currentTableCellPosition,
|
||||
}}
|
||||
key={column.fieldMetadataId}
|
||||
key={recordField.fieldMetadataItemId}
|
||||
>
|
||||
<RecordTableCellFieldContextWrapper>
|
||||
<RecordTableCellFieldContextWrapper recordField={recordField}>
|
||||
{children}
|
||||
</RecordTableCellFieldContextWrapper>
|
||||
</RecordTableCellContext.Provider>
|
||||
|
||||
+4
-10
@@ -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,
|
||||
|
||||
+29
-26
@@ -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);
|
||||
}}
|
||||
>
|
||||
<RecordTableComponentInstance recordTableId={recordTableId}>
|
||||
<RecordTableContextProvider
|
||||
recordTableId={recordTableId}
|
||||
viewBarId="viewBarId"
|
||||
objectNameSingular={CoreObjectNameSingular.Person}
|
||||
>
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
fieldDefinition: textfieldDefinition,
|
||||
recordId: 'recordId',
|
||||
isLabelIdentifier: false,
|
||||
isRecordFieldReadOnly: false,
|
||||
}}
|
||||
<RecordComponentInstanceContextsWrapper componentInstanceId={recordTableId}>
|
||||
<RecordTableComponentInstance recordTableId={recordTableId}>
|
||||
<RecordTableContextProvider
|
||||
recordTableId={recordTableId}
|
||||
viewBarId="viewBarId"
|
||||
objectNameSingular={CoreObjectNameSingular.Person}
|
||||
>
|
||||
<RecordTableRowContextProvider value={recordTableRowContextValue}>
|
||||
<RecordTableRowDraggableContextProvider
|
||||
value={recordTableRowDraggableContextValue}
|
||||
>
|
||||
<RecordTableCellContext.Provider
|
||||
value={{ ...recordTableCellContextValue }}
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
fieldDefinition: textfieldDefinition,
|
||||
recordId: 'recordId',
|
||||
isLabelIdentifier: false,
|
||||
isRecordFieldReadOnly: false,
|
||||
}}
|
||||
>
|
||||
<RecordTableRowContextProvider value={recordTableRowContextValue}>
|
||||
<RecordTableRowDraggableContextProvider
|
||||
value={recordTableRowDraggableContextValue}
|
||||
>
|
||||
{children}
|
||||
</RecordTableCellContext.Provider>
|
||||
</RecordTableRowDraggableContextProvider>
|
||||
</RecordTableRowContextProvider>
|
||||
</FieldContext.Provider>
|
||||
</RecordTableContextProvider>
|
||||
</RecordTableComponentInstance>
|
||||
<RecordTableCellContext.Provider
|
||||
value={{ ...recordTableCellContextValue }}
|
||||
>
|
||||
{children}
|
||||
</RecordTableCellContext.Provider>
|
||||
</RecordTableRowDraggableContextProvider>
|
||||
</RecordTableRowContextProvider>
|
||||
</FieldContext.Provider>
|
||||
</RecordTableContextProvider>
|
||||
</RecordTableComponentInstance>
|
||||
</RecordComponentInstanceContextsWrapper>
|
||||
</RecoilRoot>
|
||||
);
|
||||
|
||||
|
||||
+23
-18
@@ -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}
|
||||
>
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
fieldDefinition: textfieldDefinition,
|
||||
recordId: 'recordId',
|
||||
isLabelIdentifier: false,
|
||||
isRecordFieldReadOnly: false,
|
||||
}}
|
||||
<RecordComponentInstanceContextsWrapper
|
||||
componentInstanceId={recordTableId}
|
||||
>
|
||||
<RecordTableRowContextProvider value={recordTableRowContextValue}>
|
||||
<RecordTableRowDraggableContextProvider
|
||||
value={recordTableRowDraggableContextValue}
|
||||
>
|
||||
<RecordTableCellContext.Provider
|
||||
value={{ ...recordTableCellContextValue }}
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
fieldDefinition: textfieldDefinition,
|
||||
recordId: 'recordId',
|
||||
isLabelIdentifier: false,
|
||||
isRecordFieldReadOnly: false,
|
||||
}}
|
||||
>
|
||||
<RecordTableRowContextProvider value={recordTableRowContextValue}>
|
||||
<RecordTableRowDraggableContextProvider
|
||||
value={recordTableRowDraggableContextValue}
|
||||
>
|
||||
{children}
|
||||
</RecordTableCellContext.Provider>
|
||||
</RecordTableRowDraggableContextProvider>
|
||||
</RecordTableRowContextProvider>
|
||||
</FieldContext.Provider>
|
||||
<RecordTableCellContext.Provider
|
||||
value={{ ...recordTableCellContextValue }}
|
||||
>
|
||||
{children}
|
||||
</RecordTableCellContext.Provider>
|
||||
</RecordTableRowDraggableContextProvider>
|
||||
</RecordTableRowContextProvider>
|
||||
</FieldContext.Provider>
|
||||
</RecordComponentInstanceContextsWrapper>
|
||||
</RecordTableContextProvider>
|
||||
</RecordTableComponentInstance>
|
||||
</RecoilRoot>
|
||||
|
||||
+7
-10
@@ -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 = ({
|
||||
}
|
||||
>
|
||||
<StyledTd />
|
||||
{visibleTableColumns.map((column, index) => {
|
||||
{visibleRecordFields.map((recordField, index) => {
|
||||
return (
|
||||
<RecordTableColumnAggregateFooterCellContext.Provider
|
||||
key={`${column.fieldMetadataId}${currentRecordGroupId ? '-' + currentRecordGroupId : ''}`}
|
||||
key={`${recordField.fieldMetadataItemId}${currentRecordGroupId ? '-' + currentRecordGroupId : ''}`}
|
||||
value={{
|
||||
viewFieldId: column.viewFieldId || '',
|
||||
fieldMetadataId: column.fieldMetadataId,
|
||||
viewFieldId: recordField.id || '',
|
||||
fieldMetadataId: recordField.fieldMetadataItemId,
|
||||
}}
|
||||
>
|
||||
<RecordTableAggregateFooterCell
|
||||
@@ -121,7 +118,7 @@ export const RecordTableAggregateFooter = ({
|
||||
</RecordTableColumnAggregateFooterCellContext.Provider>
|
||||
);
|
||||
})}
|
||||
<td colSpan={visibleTableColumns.length - 1} />
|
||||
<td colSpan={visibleRecordFields.length - 1} />
|
||||
<td />
|
||||
<td />
|
||||
</StyledTableRow>
|
||||
|
||||
+12
-14
@@ -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 (
|
||||
<StyledColumnFooterCell
|
||||
columnWidth={Math.max(
|
||||
tableColumnsByKey[fieldMetadataId].size + 24,
|
||||
COLUMN_MIN_WIDTH,
|
||||
)}
|
||||
columnWidth={Math.max(recordField.size + 24, COLUMN_MIN_WIDTH)}
|
||||
colSpan={isFirstCell ? 2 : undefined}
|
||||
isFirstCell={isFirstCell}
|
||||
>
|
||||
|
||||
+29
-13
@@ -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<FieldMetadata>;
|
||||
};
|
||||
|
||||
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 (
|
||||
<StyledTitle
|
||||
hideTitle={!!column.isLabelIdentifier && !isRecordTableScrolledLeft}
|
||||
>
|
||||
<StyledTitle hideTitle={isLabelIdentifier && !isRecordTableScrolledLeft}>
|
||||
<StyledIcon>
|
||||
<Icon size={theme.icon.size.md} />
|
||||
</StyledIcon>
|
||||
<StyledText>{column.label}</StyledText>
|
||||
<StyledText>
|
||||
{correspondingFieldMetadataItem.foundFieldMetadataItem?.label}
|
||||
</StyledText>
|
||||
</StyledTitle>
|
||||
);
|
||||
};
|
||||
|
||||
+45
-27
@@ -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<FieldMetadata>;
|
||||
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 (
|
||||
<DropdownContent>
|
||||
|
||||
+6
-7
@@ -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<FieldMetadata>;
|
||||
recordField: RecordField;
|
||||
objectMetadataId: string;
|
||||
};
|
||||
|
||||
export const RecordTableColumnHeadWithDropdown = ({
|
||||
objectMetadataId,
|
||||
column,
|
||||
recordField,
|
||||
}: RecordTableColumnHeadWithDropdownProps) => {
|
||||
const { toggleScrollXWrapper, toggleScrollYWrapper } =
|
||||
useToggleScrollWrapper();
|
||||
@@ -32,11 +31,11 @@ export const RecordTableColumnHeadWithDropdown = ({
|
||||
<Dropdown
|
||||
onOpen={handleDropdownOpen}
|
||||
onClose={handleDropdownClose}
|
||||
dropdownId={column.fieldMetadataId + '-header'}
|
||||
clickableComponent={<RecordTableColumnHead column={column} />}
|
||||
dropdownId={recordField.fieldMetadataItemId + '-header'}
|
||||
clickableComponent={<RecordTableColumnHead recordField={recordField} />}
|
||||
dropdownComponents={
|
||||
<RecordTableColumnHeadDropdownMenu
|
||||
column={column}
|
||||
recordField={recordField}
|
||||
objectMetadataId={objectMetadataId}
|
||||
/>
|
||||
}
|
||||
|
||||
+6
-3
@@ -80,15 +80,18 @@ const StyledTableHead = styled.thead`
|
||||
`;
|
||||
|
||||
export const RecordTableHeader = () => {
|
||||
const { visibleTableColumns } = useRecordTableContextOrThrow();
|
||||
const { visibleRecordFields } = useRecordTableContextOrThrow();
|
||||
|
||||
return (
|
||||
<StyledTableHead id="record-table-header" data-select-disable>
|
||||
<tr>
|
||||
<RecordTableHeaderDragDropColumn />
|
||||
<RecordTableHeaderCheckboxColumn />
|
||||
{visibleTableColumns.map((column) => (
|
||||
<RecordTableHeaderCell key={column.fieldMetadataId} column={column} />
|
||||
{visibleRecordFields.map((recordField) => (
|
||||
<RecordTableHeaderCell
|
||||
key={recordField.fieldMetadataItemId}
|
||||
recordField={recordField}
|
||||
/>
|
||||
))}
|
||||
<RecordTableHeaderLastColumn />
|
||||
</tr>
|
||||
|
||||
+36
-51
@@ -1,20 +1,18 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly';
|
||||
import { useUpdateRecordField } from '@/object-record/record-field/hooks/useUpdateRecordField';
|
||||
import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
|
||||
import { type RecordField } from '@/object-record/record-field/types/RecordField';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
||||
import { useTableColumns } from '@/object-record/record-table/hooks/useTableColumns';
|
||||
import { RecordTableColumnHeadWithDropdown } from '@/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown';
|
||||
import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState';
|
||||
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
||||
import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState';
|
||||
import { resizeFieldOffsetComponentState } from '@/object-record/record-table/states/resizeFieldOffsetComponentState';
|
||||
import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
|
||||
import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||
import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer';
|
||||
import { type PointerEventListener } from '@/ui/utilities/pointer-event/types/PointerEventListener';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
@@ -23,9 +21,10 @@ import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-st
|
||||
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||
import { useSaveRecordFields } from '@/views/hooks/useSaveRecordFields';
|
||||
import { throwIfNotDefined } from 'twenty-shared/utils';
|
||||
import { IconPlus } from 'twenty-ui/display';
|
||||
import { LightIconButton } from 'twenty-ui/input';
|
||||
import { mapArrayToObject } from '~/utils/array/mapArrayToObject';
|
||||
|
||||
const COLUMN_MIN_WIDTH = 104;
|
||||
|
||||
@@ -109,13 +108,13 @@ const StyledHeaderIcon = styled.div`
|
||||
`;
|
||||
|
||||
type RecordTableHeaderCellProps = {
|
||||
column: ColumnDefinition<FieldMetadata>;
|
||||
recordField: RecordField;
|
||||
};
|
||||
|
||||
export const RecordTableHeaderCell = ({
|
||||
column,
|
||||
recordField,
|
||||
}: RecordTableHeaderCellProps) => {
|
||||
const { objectMetadataItem, objectPermissions, recordTableId } =
|
||||
const { objectMetadataItem, objectPermissions } =
|
||||
useRecordTableContextOrThrow();
|
||||
|
||||
const resizeFieldOffsetState = useRecoilComponentCallbackState(
|
||||
@@ -126,13 +125,6 @@ export const RecordTableHeaderCell = ({
|
||||
resizeFieldOffsetComponentState,
|
||||
);
|
||||
|
||||
const tableColumns = useRecoilComponentValue(tableColumnsComponentState);
|
||||
const tableColumnsByKey = useMemo(
|
||||
() =>
|
||||
mapArrayToObject(tableColumns, ({ fieldMetadataId }) => fieldMetadataId),
|
||||
[tableColumns],
|
||||
);
|
||||
|
||||
const [initialPointerPositionX, setInitialPointerPositionX] = useState<
|
||||
number | null
|
||||
>(null);
|
||||
@@ -140,9 +132,7 @@ export const RecordTableHeaderCell = ({
|
||||
string | null
|
||||
>(null);
|
||||
|
||||
const { handleColumnsChange } = useTableColumns({
|
||||
recordTableId,
|
||||
});
|
||||
const { saveRecordFields } = useSaveRecordFields();
|
||||
|
||||
const { updateRecordField } = useUpdateRecordField();
|
||||
|
||||
@@ -166,6 +156,8 @@ export const RecordTableHeaderCell = ({
|
||||
const handleResizeHandlerEnd = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
async () => {
|
||||
throwIfNotDefined(recordField, 'recordField');
|
||||
|
||||
if (!resizedFieldMetadataItemId) return;
|
||||
|
||||
const resizeFieldOffset = getSnapshotValue(
|
||||
@@ -174,44 +166,30 @@ export const RecordTableHeaderCell = ({
|
||||
);
|
||||
|
||||
const nextWidth = Math.round(
|
||||
Math.max(
|
||||
tableColumnsByKey[resizedFieldMetadataItemId].size +
|
||||
resizeFieldOffset,
|
||||
COLUMN_MIN_WIDTH,
|
||||
),
|
||||
Math.max(recordField.size + resizeFieldOffset, COLUMN_MIN_WIDTH),
|
||||
);
|
||||
|
||||
set(resizeFieldOffsetState, 0);
|
||||
setInitialPointerPositionX(null);
|
||||
setResizedFieldMetadataItemId(null);
|
||||
|
||||
if (nextWidth !== tableColumnsByKey[resizedFieldMetadataItemId].size) {
|
||||
const nextColumns = tableColumns.map((column) =>
|
||||
column.fieldMetadataId === resizedFieldMetadataItemId
|
||||
? { ...column, size: nextWidth }
|
||||
: column,
|
||||
if (nextWidth !== recordField.size) {
|
||||
const updatedRecordField = updateRecordField(
|
||||
resizedFieldMetadataItemId,
|
||||
{
|
||||
size: nextWidth,
|
||||
},
|
||||
);
|
||||
|
||||
updateRecordField(resizedFieldMetadataItemId, {
|
||||
size: nextWidth,
|
||||
});
|
||||
|
||||
await handleColumnsChange({
|
||||
columns: nextColumns,
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
recordTableId,
|
||||
});
|
||||
saveRecordFields([updatedRecordField]);
|
||||
}
|
||||
},
|
||||
[
|
||||
recordField,
|
||||
saveRecordFields,
|
||||
resizedFieldMetadataItemId,
|
||||
resizeFieldOffsetState,
|
||||
tableColumnsByKey,
|
||||
setResizedFieldMetadataItemId,
|
||||
tableColumns,
|
||||
handleColumnsChange,
|
||||
objectMetadataItem,
|
||||
recordTableId,
|
||||
updateRecordField,
|
||||
],
|
||||
);
|
||||
@@ -229,8 +207,13 @@ export const RecordTableHeaderCell = ({
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const { labelIdentifierFieldMetadataItem } = useRecordTableContextOrThrow();
|
||||
|
||||
const isLabelIdentifier =
|
||||
recordField.fieldMetadataItemId === labelIdentifierFieldMetadataItem?.id;
|
||||
|
||||
const disableColumnResize =
|
||||
column.isLabelIdentifier && isMobile && !isRecordTableScrolledLeft;
|
||||
isLabelIdentifier && isMobile && !isRecordTableScrolledLeft;
|
||||
|
||||
const { createNewIndexRecord } = useCreateNewIndexRecord({
|
||||
objectMetadataItem,
|
||||
@@ -261,11 +244,13 @@ export const RecordTableHeaderCell = ({
|
||||
|
||||
return (
|
||||
<StyledColumnHeaderCell
|
||||
key={column.fieldMetadataId}
|
||||
isResizing={resizedFieldMetadataItemId === column.fieldMetadataId}
|
||||
key={recordField.fieldMetadataItemId}
|
||||
isResizing={
|
||||
resizedFieldMetadataItemId === recordField.fieldMetadataItemId
|
||||
}
|
||||
columnWidth={Math.max(
|
||||
tableColumnsByKey[column.fieldMetadataId].size +
|
||||
(resizedFieldMetadataItemId === column.fieldMetadataId
|
||||
(recordField?.size ?? 0) +
|
||||
(resizedFieldMetadataItemId === recordField.fieldMetadataItemId
|
||||
? resizeFieldOffset
|
||||
: 0) +
|
||||
24,
|
||||
@@ -277,11 +262,11 @@ export const RecordTableHeaderCell = ({
|
||||
>
|
||||
<StyledColumnHeadContainer>
|
||||
<RecordTableColumnHeadWithDropdown
|
||||
column={column}
|
||||
recordField={recordField}
|
||||
objectMetadataId={objectMetadataItem.id}
|
||||
/>
|
||||
{(useIsMobile() || iconVisibility) &&
|
||||
!!column.isLabelIdentifier &&
|
||||
!!isLabelIdentifier &&
|
||||
!isReadOnly &&
|
||||
hasObjectUpdatePermissions && (
|
||||
<StyledHeaderIcon>
|
||||
@@ -299,7 +284,7 @@ export const RecordTableHeaderCell = ({
|
||||
className="cursor-col-resize"
|
||||
role="separator"
|
||||
onPointerDown={() => {
|
||||
setResizedFieldMetadataItemId(column.fieldMetadataId);
|
||||
setResizedFieldMetadataItemId(recordField.fieldMetadataItemId);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
+30
-19
@@ -2,10 +2,10 @@ import { useCallback } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useActiveFieldMetadataItems } from '@/object-metadata/hooks/useActiveFieldMetadataItems';
|
||||
import { useChangeRecordFieldVisibility } from '@/object-record/record-field/hooks/useChangeRecordFieldVisibility';
|
||||
import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { useTableColumns } from '@/object-record/record-table/hooks/useTableColumns';
|
||||
import { hiddenTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/hiddenTableColumnsComponentSelector';
|
||||
import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
@@ -13,7 +13,6 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { IconSettings, useIcons } from 'twenty-ui/display';
|
||||
import { MenuItem, UndecoratedLink } from 'twenty-ui/navigation';
|
||||
@@ -21,25 +20,22 @@ import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
|
||||
export const RecordTableHeaderPlusButtonContent = () => {
|
||||
const { t } = useLingui();
|
||||
const { objectMetadataItem, recordTableId } = useRecordTableContextOrThrow();
|
||||
const { objectMetadataItem, recordTableId, visibleRecordFields } =
|
||||
useRecordTableContextOrThrow();
|
||||
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
const hiddenTableColumns = useRecoilComponentValue(
|
||||
hiddenTableColumnsComponentSelector,
|
||||
);
|
||||
|
||||
const { getIcon } = useIcons();
|
||||
const { handleColumnVisibilityChange } = useTableColumns({
|
||||
recordTableId,
|
||||
});
|
||||
|
||||
const { changeRecordFieldVisibility } =
|
||||
useChangeRecordFieldVisibility(recordTableId);
|
||||
|
||||
const handleAddColumn = useCallback(
|
||||
(column: ColumnDefinition<FieldMetadata>) => {
|
||||
(column: Pick<ColumnDefinition<FieldMetadata>, 'fieldMetadataId'>) => {
|
||||
closeDropdown();
|
||||
handleColumnVisibilityChange({ ...column, isVisible: true });
|
||||
changeRecordFieldVisibility({ ...column, isVisible: true });
|
||||
},
|
||||
[handleColumnVisibilityChange, closeDropdown],
|
||||
[changeRecordFieldVisibility, closeDropdown],
|
||||
);
|
||||
|
||||
const location = useLocation();
|
||||
@@ -47,15 +43,30 @@ export const RecordTableHeaderPlusButtonContent = () => {
|
||||
navigationMemorizedUrlState,
|
||||
);
|
||||
|
||||
const { activeFieldMetadataItems } = useActiveFieldMetadataItems({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const availableFieldMetadataItemsToShow = activeFieldMetadataItems.filter(
|
||||
(fieldMetadataItemToFilter) =>
|
||||
!visibleRecordFields
|
||||
.map((recordField) => recordField.fieldMetadataItemId)
|
||||
.includes(fieldMetadataItemToFilter.id),
|
||||
);
|
||||
|
||||
return (
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
{hiddenTableColumns.map((column) => (
|
||||
{availableFieldMetadataItemsToShow.map((fieldMetadataItem) => (
|
||||
<MenuItem
|
||||
key={column.fieldMetadataId}
|
||||
onClick={() => handleAddColumn(column)}
|
||||
LeftIcon={getIcon(column.iconName)}
|
||||
text={column.label}
|
||||
key={fieldMetadataItem.id}
|
||||
onClick={() =>
|
||||
handleAddColumn({
|
||||
fieldMetadataId: fieldMetadataItem.id,
|
||||
})
|
||||
}
|
||||
LeftIcon={getIcon(fieldMetadataItem.icon)}
|
||||
text={fieldMetadataItem.label}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
|
||||
+2
-2
@@ -70,7 +70,7 @@ export const RecordTableActionRow = ({
|
||||
}: RecordTableActionRowProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const { visibleTableColumns } = useRecordTableContextOrThrow();
|
||||
const { visibleRecordFields } = useRecordTableContextOrThrow();
|
||||
|
||||
return (
|
||||
<StyledRecordTableDraggableTr onClick={onClick}>
|
||||
@@ -85,7 +85,7 @@ export const RecordTableActionRow = ({
|
||||
<StyledRecordTableTdTextContainer className="disable-shadow">
|
||||
<StyledText>{text}</StyledText>
|
||||
</StyledRecordTableTdTextContainer>
|
||||
<td colSpan={visibleTableColumns.length - 1} aria-hidden />
|
||||
<td colSpan={visibleRecordFields.length - 1} aria-hidden />
|
||||
<td aria-hidden />
|
||||
<td aria-hidden />
|
||||
</StyledRecordTableDraggableTr>
|
||||
|
||||
+7
-7
@@ -1,16 +1,16 @@
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||
import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
|
||||
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
|
||||
export const RecordTableCellsEmpty = () => {
|
||||
const { isSelected } = useRecordTableRowContextOrThrow();
|
||||
|
||||
const visibleTableColumns = useRecoilComponentValue(
|
||||
visibleTableColumnsComponentSelector,
|
||||
);
|
||||
const { visibleRecordFields } = useRecordTableContextOrThrow();
|
||||
|
||||
return visibleTableColumns.map((column) => (
|
||||
<RecordTableTd isSelected={isSelected} key={column.fieldMetadataId} />
|
||||
return visibleRecordFields.map((recordField) => (
|
||||
<RecordTableTd
|
||||
isSelected={isSelected}
|
||||
key={recordField.fieldMetadataItemId}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
+14
-14
@@ -1,10 +1,9 @@
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||
import { useRecordTableRowDraggableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowDraggableContext';
|
||||
import { RecordTableCell } from '@/object-record/record-table/record-table-cell/components/RecordTableCell';
|
||||
import { RecordTableCellWrapper } from '@/object-record/record-table/record-table-cell/components/RecordTableCellWrapper';
|
||||
import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
|
||||
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { isNonEmptyArray } from '~/utils/isNonEmptyArray';
|
||||
|
||||
export const RecordTableCellsVisible = () => {
|
||||
@@ -12,37 +11,38 @@ export const RecordTableCellsVisible = () => {
|
||||
|
||||
const { isDragging } = useRecordTableRowDraggableContextOrThrow();
|
||||
|
||||
const visibleTableColumns = useRecoilComponentValue(
|
||||
visibleTableColumnsComponentSelector,
|
||||
);
|
||||
const { visibleRecordFields } = useRecordTableContextOrThrow();
|
||||
|
||||
if (!isNonEmptyArray(visibleTableColumns)) {
|
||||
if (!isNonEmptyArray(visibleRecordFields)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tableColumnsAfterFirst = visibleTableColumns.slice(1);
|
||||
const recordFieldsAfterFirst = visibleRecordFields.slice(1);
|
||||
|
||||
return (
|
||||
<>
|
||||
<RecordTableCellWrapper column={visibleTableColumns[0]} columnIndex={0}>
|
||||
<RecordTableCellWrapper
|
||||
recordField={visibleRecordFields[0]}
|
||||
recordFieldIndex={0}
|
||||
>
|
||||
<RecordTableTd
|
||||
isSelected={isSelected}
|
||||
isDragging={isDragging}
|
||||
width={visibleTableColumns[0].size}
|
||||
width={visibleRecordFields[0].size}
|
||||
>
|
||||
<RecordTableCell />
|
||||
</RecordTableTd>
|
||||
</RecordTableCellWrapper>
|
||||
{tableColumnsAfterFirst.map((column, columnIndex) => (
|
||||
{recordFieldsAfterFirst.map((recordField, recordFieldIndex) => (
|
||||
<RecordTableCellWrapper
|
||||
key={column.fieldMetadataId}
|
||||
column={column}
|
||||
columnIndex={columnIndex + 1}
|
||||
key={recordField.fieldMetadataItemId}
|
||||
recordField={recordField}
|
||||
recordFieldIndex={recordFieldIndex + 1}
|
||||
>
|
||||
<RecordTableTd
|
||||
isSelected={isSelected}
|
||||
isDragging={isDragging}
|
||||
width={column.size}
|
||||
width={recordField.size}
|
||||
>
|
||||
<RecordTableCell />
|
||||
</RecordTableTd>
|
||||
|
||||
+3
-8
@@ -11,9 +11,7 @@ import { RecordTableTd } from '@/object-record/record-table/record-table-cell/co
|
||||
import { RecordTableRecordGroupStickyEffect } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupStickyEffect';
|
||||
import { useAggregateRecordsForRecordTableSection } from '@/object-record/record-table/record-table-section/hooks/useAggregateRecordsForRecordTableSection';
|
||||
import { isRecordGroupTableSectionToggledComponentState } from '@/object-record/record-table/record-table-section/states/isRecordGroupTableSectionToggledComponentState';
|
||||
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
||||
import { useRecoilComponentFamilyState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Tag } from 'twenty-ui/components';
|
||||
@@ -57,11 +55,8 @@ export const RecordTableRecordGroupSection = () => {
|
||||
|
||||
const currentRecordGroupId = useCurrentRecordGroupId();
|
||||
|
||||
const visibleColumns = useRecoilComponentValue(
|
||||
visibleTableColumnsComponentSelector,
|
||||
);
|
||||
|
||||
const { objectMetadataItem } = useRecordTableContextOrThrow();
|
||||
const { visibleRecordFields, objectMetadataItem } =
|
||||
useRecordTableContextOrThrow();
|
||||
|
||||
const { aggregateValue, aggregateLabel } =
|
||||
useAggregateRecordsForRecordTableSection();
|
||||
@@ -121,7 +116,7 @@ export const RecordTableRecordGroupSection = () => {
|
||||
/>
|
||||
<RecordTableRecordGroupStickyEffect />
|
||||
</StyledRecordGroupSection>
|
||||
<StyledEmptyTd colSpan={visibleColumns.length - 1} />
|
||||
<StyledEmptyTd colSpan={visibleRecordFields.length - 1} />
|
||||
<StyledEmptyTd />
|
||||
<StyledEmptyTd />
|
||||
</StyledTrContainer>
|
||||
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||
import { type ColumnDefinition } from '../types/ColumnDefinition';
|
||||
|
||||
export const availableTableColumnsComponentState = createComponentState<
|
||||
ColumnDefinition<FieldMetadata>[]
|
||||
>({
|
||||
key: 'availableTableColumnsComponentState',
|
||||
defaultValue: [],
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
});
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
import { availableTableColumnsComponentState } from '@/object-record/record-table/states/availableTableColumnsComponentState';
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
|
||||
import { createComponentSelector } from '@/ui/utilities/state/component-state/utils/createComponentSelector';
|
||||
import { mapArrayToObject } from '~/utils/array/mapArrayToObject';
|
||||
|
||||
export const hiddenTableColumnsComponentSelector = createComponentSelector({
|
||||
key: 'hiddenTableColumnsComponentSelector',
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
get:
|
||||
({ instanceId }) =>
|
||||
({ get }) => {
|
||||
const tableColumns = get(
|
||||
tableColumnsComponentState.atomFamily({ instanceId }),
|
||||
);
|
||||
const availableColumns = get(
|
||||
availableTableColumnsComponentState.atomFamily({ instanceId }),
|
||||
);
|
||||
const tableColumnsByKey = mapArrayToObject(
|
||||
tableColumns,
|
||||
({ fieldMetadataId }) => fieldMetadataId,
|
||||
);
|
||||
|
||||
const hiddenColumns = availableColumns
|
||||
.filter(
|
||||
({ fieldMetadataId }) =>
|
||||
!tableColumnsByKey[fieldMetadataId]?.isVisible,
|
||||
)
|
||||
.map((availableColumn) => {
|
||||
const { fieldMetadataId } = availableColumn;
|
||||
const existingTableColumn = tableColumnsByKey[fieldMetadataId];
|
||||
|
||||
return {
|
||||
...(existingTableColumn || availableColumn),
|
||||
isVisible: false,
|
||||
};
|
||||
});
|
||||
|
||||
return hiddenColumns;
|
||||
},
|
||||
});
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
|
||||
import { createComponentSelector } from '@/ui/utilities/state/component-state/utils/createComponentSelector';
|
||||
|
||||
export const numberOfTableColumnsComponentSelector = createComponentSelector({
|
||||
key: 'numberOfTableColumnsComponentSelector',
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
get:
|
||||
({ instanceId }) =>
|
||||
({ get }) =>
|
||||
get(tableColumnsComponentState.atomFamily({ instanceId })).length,
|
||||
});
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
|
||||
import { createComponentSelector } from '@/ui/utilities/state/component-state/utils/createComponentSelector';
|
||||
|
||||
export const visibleTableColumnsComponentSelector = createComponentSelector({
|
||||
key: 'visibleTableColumnsComponentSelector',
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
get:
|
||||
({ instanceId }) =>
|
||||
({ get }) => {
|
||||
const columns = get(
|
||||
tableColumnsComponentState.atomFamily({ instanceId }),
|
||||
);
|
||||
|
||||
return columns
|
||||
.filter((column) => column.isVisible)
|
||||
.sort((columnA, columnB) => columnA.position - columnB.position);
|
||||
},
|
||||
});
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||
import { type ColumnDefinition } from '../types/ColumnDefinition';
|
||||
|
||||
export const tableColumnsComponentState = createComponentState<
|
||||
ColumnDefinition<FieldMetadata>[]
|
||||
>({
|
||||
key: 'tableColumnsComponentState',
|
||||
defaultValue: [],
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
});
|
||||
+22
-19
@@ -4,13 +4,13 @@ import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainCo
|
||||
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
||||
import { useSetAvailableTableColumns } from '@/object-record/record-table/hooks/useSetAvailableTableColumns';
|
||||
import { useSetTableColumns } from '@/object-record/record-table/hooks/useSetTableColumns';
|
||||
|
||||
import { currentRecordFieldsComponentState } from '@/object-record/record-field/states/currentRecordFieldsComponentState';
|
||||
import { type RecordField } from '@/object-record/record-field/types/RecordField';
|
||||
import { SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS } from '@/sign-in-background-mock/constants/SignInBackgroundMockColumnDefinitions';
|
||||
import { SIGN_IN_BACKGROUND_MOCK_VIEW_FIELDS } from '@/sign-in-background-mock/constants/SignInBackgroundMockViewFields';
|
||||
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
import { useInitViewBar } from '@/views/hooks/useInitViewBar';
|
||||
import { mapViewFieldsToColumnDefinitions } from '@/views/utils/mapViewFieldsToColumnDefinitions';
|
||||
|
||||
type SignInBackgroundMockContainerEffectProps = {
|
||||
objectNamePlural: string;
|
||||
@@ -31,10 +31,10 @@ export const SignInBackgroundMockContainerEffect = ({
|
||||
MAIN_CONTEXT_STORE_INSTANCE_ID,
|
||||
);
|
||||
|
||||
const { setAvailableTableColumns } =
|
||||
useSetAvailableTableColumns(recordTableId);
|
||||
|
||||
const { setTableColumns } = useSetTableColumns();
|
||||
const setCurrentRecordFields = useSetRecoilComponentState(
|
||||
currentRecordFieldsComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const { objectNameSingular } = useObjectNameSingularFromPlural({
|
||||
objectNamePlural,
|
||||
@@ -52,17 +52,21 @@ export const SignInBackgroundMockContainerEffect = ({
|
||||
|
||||
setAvailableFieldDefinitions?.(SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS);
|
||||
|
||||
setAvailableTableColumns(SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS);
|
||||
|
||||
setTableColumns(
|
||||
mapViewFieldsToColumnDefinitions({
|
||||
viewFields: SIGN_IN_BACKGROUND_MOCK_VIEW_FIELDS,
|
||||
columnDefinitions: SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS,
|
||||
}),
|
||||
recordTableId,
|
||||
objectMetadataItem.id,
|
||||
const recordFields = SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS.filter(
|
||||
(fieldDefinition) => fieldDefinition.fieldMetadataId !== '',
|
||||
).map(
|
||||
(columnDefinitionMock) =>
|
||||
({
|
||||
fieldMetadataItemId: columnDefinitionMock.fieldMetadataId,
|
||||
id: columnDefinitionMock.fieldMetadataId,
|
||||
isVisible: columnDefinitionMock.isVisible,
|
||||
position: columnDefinitionMock.position,
|
||||
size: columnDefinitionMock.size,
|
||||
}) satisfies RecordField as RecordField,
|
||||
);
|
||||
|
||||
setCurrentRecordFields(recordFields);
|
||||
|
||||
if (contextStoreCurrentObjectMetadataItemId !== objectMetadataItem.id) {
|
||||
setContextStoreCurrentObjectMetadataItemId(objectMetadataItem.id);
|
||||
}
|
||||
@@ -70,11 +74,10 @@ export const SignInBackgroundMockContainerEffect = ({
|
||||
setViewObjectMetadataId,
|
||||
setAvailableFieldDefinitions,
|
||||
objectMetadataItem,
|
||||
setAvailableTableColumns,
|
||||
setTableColumns,
|
||||
recordTableId,
|
||||
setContextStoreCurrentObjectMetadataItemId,
|
||||
contextStoreCurrentObjectMetadataItemId,
|
||||
setCurrentRecordFields,
|
||||
]);
|
||||
|
||||
return <></>;
|
||||
|
||||
+111
-73
@@ -1,47 +1,20 @@
|
||||
/* eslint-disable @nx/workspace-max-consts-per-file */
|
||||
import { type FieldMetadata } from '@/object-record/record-field/ui/types/FieldMetadata';
|
||||
import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
|
||||
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
|
||||
import { findByProperty } from 'twenty-shared/utils';
|
||||
import { FieldMetadataType, RelationType } from '~/generated-metadata/graphql';
|
||||
import { getMockCompanyObjectMetadataItem } from '~/testing/mock-data/companies';
|
||||
|
||||
const COMPANY_MOCK_OBJECT = getMockCompanyObjectMetadataItem();
|
||||
|
||||
export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
|
||||
[
|
||||
{
|
||||
position: 0,
|
||||
fieldMetadataId: '20202020-5e4e-4007-a630-8a2617914889',
|
||||
label: 'Domain Name',
|
||||
size: 100,
|
||||
type: FieldMetadataType.LINKS,
|
||||
metadata: {
|
||||
fieldName: 'domainName',
|
||||
placeHolder: 'Domain Name',
|
||||
relationObjectMetadataNameSingular: '',
|
||||
relationObjectMetadataNamePlural: '',
|
||||
objectMetadataNameSingular: 'company',
|
||||
},
|
||||
iconName: 'IconLink',
|
||||
isVisible: true,
|
||||
defaultValue: '',
|
||||
},
|
||||
{
|
||||
position: 1,
|
||||
fieldMetadataId: '20202020-7fbd-41ad-b64d-25a15ff62f04',
|
||||
label: 'Employees',
|
||||
size: 100,
|
||||
type: FieldMetadataType.NUMBER,
|
||||
metadata: {
|
||||
fieldName: 'employees',
|
||||
placeHolder: 'Employees',
|
||||
relationObjectMetadataNameSingular: '',
|
||||
relationObjectMetadataNamePlural: '',
|
||||
objectMetadataNameSingular: 'company',
|
||||
},
|
||||
iconName: 'IconUsers',
|
||||
isVisible: true,
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
position: 2,
|
||||
fieldMetadataId: 'REPLACE_ME',
|
||||
fieldMetadataId:
|
||||
COMPANY_MOCK_OBJECT.fields.find(findByProperty('name', 'name'))?.id ??
|
||||
'',
|
||||
label: 'Name',
|
||||
size: 100,
|
||||
type: FieldMetadataType.TEXT,
|
||||
@@ -56,26 +29,68 @@ export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
|
||||
isVisible: true,
|
||||
defaultValue: '',
|
||||
},
|
||||
{
|
||||
position: 1,
|
||||
fieldMetadataId:
|
||||
COMPANY_MOCK_OBJECT.fields.find(findByProperty('name', 'domainName'))
|
||||
?.id ?? '',
|
||||
label: 'Domain Name',
|
||||
size: 100,
|
||||
type: FieldMetadataType.LINKS,
|
||||
metadata: {
|
||||
fieldName: 'domainName',
|
||||
placeHolder: 'Domain Name',
|
||||
relationObjectMetadataNameSingular: '',
|
||||
relationObjectMetadataNamePlural: '',
|
||||
objectMetadataNameSingular: 'company',
|
||||
},
|
||||
iconName: 'IconLink',
|
||||
isVisible: true,
|
||||
defaultValue: '',
|
||||
},
|
||||
{
|
||||
position: 2,
|
||||
fieldMetadataId:
|
||||
COMPANY_MOCK_OBJECT.fields.find(findByProperty('name', 'employees'))
|
||||
?.id ?? '',
|
||||
label: 'Employees',
|
||||
size: 100,
|
||||
type: FieldMetadataType.NUMBER,
|
||||
metadata: {
|
||||
fieldName: 'employees',
|
||||
placeHolder: 'Employees',
|
||||
relationObjectMetadataNameSingular: '',
|
||||
relationObjectMetadataNamePlural: '',
|
||||
objectMetadataNameSingular: 'company',
|
||||
},
|
||||
iconName: 'IconUsers',
|
||||
isVisible: true,
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
position: 3,
|
||||
fieldMetadataId: '20202020-e7c8-4771-8cc4-ce0e8c36a3c0',
|
||||
label: 'Favorites',
|
||||
fieldMetadataId:
|
||||
COMPANY_MOCK_OBJECT.fields.find(findByProperty('name', 'people'))?.id ??
|
||||
'',
|
||||
label: 'People',
|
||||
size: 100,
|
||||
type: FieldMetadataType.RELATION,
|
||||
metadata: {
|
||||
fieldName: 'favorites',
|
||||
fieldName: 'people',
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
relationObjectMetadataNameSingular: '',
|
||||
relationObjectMetadataNamePlural: '',
|
||||
objectMetadataNameSingular: 'company',
|
||||
},
|
||||
iconName: 'IconHeart',
|
||||
iconName: 'IconUsers',
|
||||
isVisible: true,
|
||||
defaultValue: [],
|
||||
},
|
||||
{
|
||||
position: 4,
|
||||
fieldMetadataId: '20202020-ad10-4117-a039-3f04b7a5f939',
|
||||
fieldMetadataId:
|
||||
COMPANY_MOCK_OBJECT.fields.find(findByProperty('name', 'address'))
|
||||
?.id ?? '',
|
||||
label: 'Address',
|
||||
size: 100,
|
||||
type: FieldMetadataType.ADDRESS,
|
||||
@@ -92,7 +107,9 @@ export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
|
||||
},
|
||||
{
|
||||
position: 5,
|
||||
fieldMetadataId: '20202020-0739-495d-8e70-c0807f6b2268',
|
||||
fieldMetadataId:
|
||||
COMPANY_MOCK_OBJECT.fields.find(findByProperty('name', 'accountOwner'))
|
||||
?.id ?? '',
|
||||
label: 'Account Owner',
|
||||
size: 100,
|
||||
type: FieldMetadataType.RELATION,
|
||||
@@ -109,24 +126,9 @@ export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
|
||||
},
|
||||
{
|
||||
position: 6,
|
||||
fieldMetadataId: '20202020-68b4-4c8e-af19-738eba2a42a5',
|
||||
label: 'People',
|
||||
size: 100,
|
||||
type: FieldMetadataType.RELATION,
|
||||
metadata: {
|
||||
fieldName: 'people',
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
relationObjectMetadataNameSingular: '',
|
||||
relationObjectMetadataNamePlural: '',
|
||||
objectMetadataNameSingular: 'company',
|
||||
},
|
||||
iconName: 'IconUsers',
|
||||
isVisible: true,
|
||||
defaultValue: [],
|
||||
},
|
||||
{
|
||||
position: 7,
|
||||
fieldMetadataId: '20202020-61af-4ffd-b79b-baed6db8ad11',
|
||||
fieldMetadataId:
|
||||
COMPANY_MOCK_OBJECT.fields.find(findByProperty('name', 'attachments'))
|
||||
?.id ?? '',
|
||||
label: 'Attachments',
|
||||
size: 100,
|
||||
type: FieldMetadataType.RELATION,
|
||||
@@ -142,8 +144,10 @@ export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
|
||||
defaultValue: [],
|
||||
},
|
||||
{
|
||||
position: 8,
|
||||
fieldMetadataId: '20202020-4dc2-47c9-bb15-6e6f19ba9e46',
|
||||
position: 7,
|
||||
fieldMetadataId:
|
||||
COMPANY_MOCK_OBJECT.fields.find(findByProperty('name', 'createdAt'))
|
||||
?.id ?? '',
|
||||
label: 'Creation date',
|
||||
size: 100,
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
@@ -159,8 +163,11 @@ export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
|
||||
defaultValue: '',
|
||||
},
|
||||
{
|
||||
position: 9,
|
||||
fieldMetadataId: '20202020-9e9f-4235-98b2-c76f3e2d281e',
|
||||
position: 8,
|
||||
fieldMetadataId:
|
||||
COMPANY_MOCK_OBJECT.fields.find(
|
||||
findByProperty('name', 'idealCustomerProfile'),
|
||||
)?.id ?? '',
|
||||
label: 'ICP',
|
||||
size: 100,
|
||||
type: FieldMetadataType.BOOLEAN,
|
||||
@@ -176,8 +183,10 @@ export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
position: 10,
|
||||
fieldMetadataId: '20202020-a61d-4b78-b998-3fd88b4f73a1',
|
||||
position: 9,
|
||||
fieldMetadataId:
|
||||
COMPANY_MOCK_OBJECT.fields.find(findByProperty('name', 'linkedinLink'))
|
||||
?.id ?? '',
|
||||
label: 'Linkedin',
|
||||
size: 100,
|
||||
type: FieldMetadataType.LINKS,
|
||||
@@ -193,8 +202,10 @@ export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
|
||||
defaultValue: '',
|
||||
},
|
||||
{
|
||||
position: 11,
|
||||
fieldMetadataId: '20202020-e3fc-46ff-b552-3e757843f06e',
|
||||
position: 10,
|
||||
fieldMetadataId:
|
||||
COMPANY_MOCK_OBJECT.fields.find(findByProperty('name', 'opportunities'))
|
||||
?.id ?? '',
|
||||
label: 'Opportunities',
|
||||
size: 100,
|
||||
type: FieldMetadataType.RELATION,
|
||||
@@ -210,8 +221,10 @@ export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
|
||||
defaultValue: [],
|
||||
},
|
||||
{
|
||||
position: 12,
|
||||
fieldMetadataId: '20202020-46e3-479a-b8f4-77137c74daa6',
|
||||
position: 11,
|
||||
fieldMetadataId:
|
||||
COMPANY_MOCK_OBJECT.fields.find(findByProperty('name', 'xLink'))?.id ??
|
||||
'',
|
||||
label: 'X',
|
||||
size: 100,
|
||||
type: FieldMetadataType.LINKS,
|
||||
@@ -227,8 +240,11 @@ export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
|
||||
defaultValue: '',
|
||||
},
|
||||
{
|
||||
position: 13,
|
||||
fieldMetadataId: '20202020-4a2e-4b41-8562-279963e8947e',
|
||||
position: 12,
|
||||
fieldMetadataId:
|
||||
COMPANY_MOCK_OBJECT.fields.find(
|
||||
findByProperty('name', 'activityTargets'),
|
||||
)?.id ?? '',
|
||||
label: 'Activities',
|
||||
size: 100,
|
||||
type: FieldMetadataType.RELATION,
|
||||
@@ -244,8 +260,11 @@ export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
|
||||
defaultValue: [],
|
||||
},
|
||||
{
|
||||
position: 14,
|
||||
fieldMetadataId: '20202020-4a5a-466f-92d9-c3870d9502a9',
|
||||
position: 13,
|
||||
fieldMetadataId:
|
||||
COMPANY_MOCK_OBJECT.fields.find(
|
||||
findByProperty('name', 'annualRecurringRevenue'),
|
||||
)?.id ?? '',
|
||||
label: 'ARR',
|
||||
size: 100,
|
||||
type: FieldMetadataType.CURRENCY,
|
||||
@@ -260,5 +279,24 @@ export const SIGN_IN_BACKGROUND_MOCK_COLUMN_DEFINITIONS = (
|
||||
isVisible: true,
|
||||
defaultValue: 0,
|
||||
},
|
||||
{
|
||||
position: 14,
|
||||
fieldMetadataId:
|
||||
COMPANY_MOCK_OBJECT.fields.find(findByProperty('name', 'favorites'))
|
||||
?.id ?? '',
|
||||
label: 'Favorites',
|
||||
size: 100,
|
||||
type: FieldMetadataType.RELATION,
|
||||
metadata: {
|
||||
fieldName: 'favorites',
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
relationObjectMetadataNameSingular: '',
|
||||
relationObjectMetadataNamePlural: '',
|
||||
objectMetadataNameSingular: 'company',
|
||||
},
|
||||
iconName: 'IconHeart',
|
||||
isVisible: true,
|
||||
defaultValue: [],
|
||||
},
|
||||
] satisfies ColumnDefinition<FieldMetadata>[]
|
||||
).filter(filterAvailableTableColumns);
|
||||
|
||||
+25
-1
@@ -1,4 +1,9 @@
|
||||
export const SIGN_IN_BACKGROUND_MOCK_COMPANIES = [
|
||||
import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
|
||||
import { type RecordGqlConnection } from '@/object-record/graphql/types/RecordGqlConnection';
|
||||
import { type RecordGqlEdge } from '@/object-record/graphql/types/RecordGqlEdge';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const baseMock = [
|
||||
{
|
||||
__typename: 'Company',
|
||||
id: '04b2e9f5-0713-40a5-8216-82802401d33e',
|
||||
@@ -1360,3 +1365,22 @@ export const SIGN_IN_BACKGROUND_MOCK_COMPANIES = [
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// eslint-disable-next-line @nx/workspace-max-consts-per-file, @typescript-eslint/naming-convention
|
||||
const baseMockToRecordConnection = {
|
||||
edges: baseMock.map(
|
||||
(mockCompany) =>
|
||||
({
|
||||
node: mockCompany,
|
||||
cursor: mockCompany.id,
|
||||
__typename: 'CompanyEdge',
|
||||
}) as RecordGqlEdge,
|
||||
) as any,
|
||||
pageInfo: {},
|
||||
__typename: 'CompanyConnection',
|
||||
} as RecordGqlConnection;
|
||||
|
||||
// eslint-disable-next-line @nx/workspace-max-consts-per-file
|
||||
export const SIGN_IN_BACKGROUND_MOCK_COMPANIES = getRecordsFromRecordConnection(
|
||||
{ recordConnection: baseMockToRecordConnection },
|
||||
);
|
||||
|
||||
+4
-6
@@ -1,7 +1,7 @@
|
||||
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 { useChangeRecordFieldVisibility } from '@/object-record/record-field/hooks/useChangeRecordFieldVisibility';
|
||||
import { currentRecordFieldsComponentState } from '@/object-record/record-field/states/currentRecordFieldsComponentState';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
@@ -15,10 +15,8 @@ export const ViewFieldsHiddenDropdownSection = () => {
|
||||
ObjectOptionsDropdownContext,
|
||||
);
|
||||
|
||||
const { handleColumnVisibilityChange } = useObjectOptionsForTable(
|
||||
recordIndexId,
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
const { changeRecordFieldVisibility } =
|
||||
useChangeRecordFieldVisibility(recordIndexId);
|
||||
|
||||
const { handleBoardFieldVisibilityChange } = useObjectOptionsForBoard({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
@@ -29,7 +27,7 @@ export const ViewFieldsHiddenDropdownSection = () => {
|
||||
const handleChangeFieldVisibility =
|
||||
viewType === ViewType.Kanban
|
||||
? handleBoardFieldVisibilityChange
|
||||
: handleColumnVisibilityChange;
|
||||
: changeRecordFieldVisibility;
|
||||
|
||||
const currentRecordFields = useRecoilComponentValue(
|
||||
currentRecordFieldsComponentState,
|
||||
|
||||
+11
-7
@@ -3,9 +3,10 @@ import { type DropResult, type ResponderProvided } from '@hello-pangea/dnd';
|
||||
import { useGetFieldMetadataItemByIdOrThrow } from '@/object-metadata/hooks/useGetFieldMetadataItemById';
|
||||
import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem';
|
||||
import { useObjectOptionsForBoard } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForBoard';
|
||||
import { useObjectOptionsForTable } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForTable';
|
||||
import { useProcessOptionDropdownDragEnd } from '@/object-record/object-options-dropdown/hooks/useProcessOptionDropdownDragEnd';
|
||||
import { ObjectOptionsDropdownContext } from '@/object-record/object-options-dropdown/states/contexts/ObjectOptionsDropdownContext';
|
||||
import { visibleAndReadableRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleAndReadableRecordFieldsComponentSelector';
|
||||
import { useChangeRecordFieldVisibility } from '@/object-record/record-field/hooks/useChangeRecordFieldVisibility';
|
||||
import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector';
|
||||
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';
|
||||
@@ -22,8 +23,8 @@ export const ViewFieldsVisibleDropdownSection = () => {
|
||||
ObjectOptionsDropdownContext,
|
||||
);
|
||||
|
||||
const { handleReorderColumns, handleColumnVisibilityChange } =
|
||||
useObjectOptionsForTable(recordIndexId, objectMetadataItem.id);
|
||||
const { processOptionDropdownDragEnd } =
|
||||
useProcessOptionDropdownDragEnd(recordIndexId);
|
||||
|
||||
const { handleReorderBoardFields, handleBoardFieldVisibilityChange } =
|
||||
useObjectOptionsForBoard({
|
||||
@@ -38,12 +39,15 @@ export const ViewFieldsVisibleDropdownSection = () => {
|
||||
const handleReorderFields =
|
||||
viewType === ViewType.Kanban
|
||||
? handleReorderBoardFields
|
||||
: handleReorderColumns;
|
||||
: processOptionDropdownDragEnd;
|
||||
|
||||
const { changeRecordFieldVisibility } =
|
||||
useChangeRecordFieldVisibility(recordIndexId);
|
||||
|
||||
const handleChangeFieldVisibility =
|
||||
viewType === ViewType.Kanban
|
||||
? handleBoardFieldVisibilityChange
|
||||
: handleColumnVisibilityChange;
|
||||
: changeRecordFieldVisibility;
|
||||
|
||||
const handleDragEnd = (result: DropResult, provided: ResponderProvided) => {
|
||||
handleReorderFields(result, provided);
|
||||
@@ -55,7 +59,7 @@ export const ViewFieldsVisibleDropdownSection = () => {
|
||||
getLabelIdentifierFieldMetadataItem(objectMetadataItem);
|
||||
|
||||
const visibleRecordFields = useRecoilComponentValue(
|
||||
visibleAndReadableRecordFieldsComponentSelector,
|
||||
visibleRecordFieldsComponentSelector,
|
||||
);
|
||||
|
||||
const nonDraggableRecordField = visibleRecordFields.find(
|
||||
|
||||
+11
-9
@@ -4,11 +4,9 @@ import { type TaskGroups } from '@/activities/tasks/components/TaskGroups';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
|
||||
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
|
||||
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
import { ViewBarFilterDropdown } from '@/views/components/ViewBarFilterDropdown';
|
||||
@@ -16,6 +14,8 @@ import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewCompon
|
||||
|
||||
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
|
||||
import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper';
|
||||
import { currentRecordFieldsComponentState } from '@/object-record/record-field/states/currentRecordFieldsComponentState';
|
||||
import { type RecordField } from '@/object-record/record-field/types/RecordField';
|
||||
import { VIEW_BAR_FILTER_DROPDOWN_ID } from '@/views/constants/ViewBarFilterDropdownId';
|
||||
import { coreViewsState } from '@/views/states/coreViewState';
|
||||
import { within } from '@storybook/test';
|
||||
@@ -45,8 +45,8 @@ const meta: Meta<typeof ViewBarFilterDropdown> = {
|
||||
)!;
|
||||
const instanceId = companyObjectMetadataItem.id;
|
||||
|
||||
const setTableColumns = useSetRecoilComponentState(
|
||||
tableColumnsComponentState,
|
||||
const setCurrentRecordFields = useSetRecoilComponentState(
|
||||
currentRecordFieldsComponentState,
|
||||
instanceId,
|
||||
);
|
||||
|
||||
@@ -68,14 +68,16 @@ const meta: Meta<typeof ViewBarFilterDropdown> = {
|
||||
|
||||
const columns = companyObjectMetadataItem.fields.map(
|
||||
(fieldMetadataItem, index) =>
|
||||
formatFieldMetadataItemAsColumnDefinition({
|
||||
field: fieldMetadataItem,
|
||||
objectMetadataItem: companyObjectMetadataItem,
|
||||
({
|
||||
id: fieldMetadataItem.id,
|
||||
fieldMetadataItemId: fieldMetadataItem.id,
|
||||
isVisible: true,
|
||||
position: index,
|
||||
}),
|
||||
size: 100,
|
||||
}) satisfies RecordField,
|
||||
);
|
||||
|
||||
setTableColumns(columns);
|
||||
setCurrentRecordFields(columns);
|
||||
|
||||
return (
|
||||
<RecordIndexContextProvider
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { type RecordField } from '@/object-record/record-field/types/RecordField';
|
||||
import { useSaveCurrentViewFields } from '@/views/hooks/useSaveCurrentViewFields';
|
||||
import { mapRecordFieldToViewField } from '@/views/utils/mapRecordFieldToViewField';
|
||||
|
||||
export const useSaveRecordFields = () => {
|
||||
const { saveViewFields } = useSaveCurrentViewFields();
|
||||
|
||||
const saveRecordFields = (recordFields: RecordField[]) => {
|
||||
saveViewFields(recordFields.map(mapRecordFieldToViewField));
|
||||
};
|
||||
|
||||
return {
|
||||
saveRecordFields,
|
||||
};
|
||||
};
|
||||
@@ -3,10 +3,14 @@ import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||
import { getActionMenuIdFromRecordIndexId } from '@/action-menu/utils/getActionMenuIdFromRecordIndexId';
|
||||
import { labelIdentifierFieldMetadataItemSelector } from '@/object-metadata/states/labelIdentifierFieldMetadataItemSelector';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { RecordComponentInstanceContextsWrapper } from '@/object-record/components/RecordComponentInstanceContextsWrapper';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { currentRecordFieldsComponentState } from '@/object-record/record-field/states/currentRecordFieldsComponentState';
|
||||
import { visibleRecordFieldsComponentSelector } from '@/object-record/record-field/states/visibleRecordFieldsComponentSelector';
|
||||
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { useLoadRecordIndexStates } from '@/object-record/record-index/hooks/useLoadRecordIndexStates';
|
||||
import { RecordTableBodyContextProvider } from '@/object-record/record-table/contexts/RecordTableBodyContext';
|
||||
@@ -16,12 +20,13 @@ import {
|
||||
} from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { useSetRecordTableData } from '@/object-record/record-table/hooks/internal/useSetRecordTableData';
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
||||
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
|
||||
import { getObjectPermissionsFromMapByObjectMetadataId } from '@/settings/roles/role-permissions/objects-permissions/utils/getObjectPermissionsFromMapByObjectMetadataId';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||
import { type View } from '@/views/types/View';
|
||||
import { mapViewFieldToRecordField } from '@/views/utils/mapViewFieldToRecordField';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { getCompaniesMock } from '~/testing/mock-data/companies';
|
||||
@@ -35,6 +40,9 @@ const InternalTableStateLoaderEffect = ({
|
||||
}) => {
|
||||
const { recordTableId } = useRecordTableContextOrThrow();
|
||||
const { loadRecordIndexStates } = useLoadRecordIndexStates();
|
||||
const setCurrentRecordFields = useSetRecoilComponentState(
|
||||
currentRecordFieldsComponentState,
|
||||
);
|
||||
|
||||
const setRecordTableData = useSetRecordTableData({
|
||||
recordTableId,
|
||||
@@ -54,7 +62,18 @@ const InternalTableStateLoaderEffect = ({
|
||||
setRecordTableData({
|
||||
records: getCompaniesMock(),
|
||||
});
|
||||
}, [loadRecordIndexStates, objectMetadataItem, setRecordTableData, view]);
|
||||
const recordFields = view.viewFields
|
||||
.map(mapViewFieldToRecordField)
|
||||
.filter(isDefined);
|
||||
|
||||
setCurrentRecordFields(recordFields);
|
||||
}, [
|
||||
loadRecordIndexStates,
|
||||
objectMetadataItem,
|
||||
setRecordTableData,
|
||||
view,
|
||||
setCurrentRecordFields,
|
||||
]);
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -66,11 +85,53 @@ const InternalTableContextProviders = ({
|
||||
children: React.ReactNode;
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
}) => {
|
||||
const visibleTableColumns = useRecoilComponentValue(
|
||||
visibleTableColumnsComponentSelector,
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
const currentRecordFields = useRecoilComponentValue(
|
||||
currentRecordFieldsComponentState,
|
||||
'record-index',
|
||||
);
|
||||
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
const visibleRecordFields = useRecoilComponentValue(
|
||||
visibleRecordFieldsComponentSelector,
|
||||
'record-index',
|
||||
);
|
||||
|
||||
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: objectMetadataItem,
|
||||
position:
|
||||
recordFieldByFieldMetadataItemId[fieldMetadataItem.id]?.position ?? 0,
|
||||
labelWidth:
|
||||
recordFieldByFieldMetadataItemId[fieldMetadataItem.id]?.size ?? 0,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
const labelIdentifierFieldMetadataItem = useRecoilValue(
|
||||
labelIdentifierFieldMetadataItemSelector({
|
||||
objectMetadataItemId: objectMetadataItem.id,
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<RecordIndexContextProvider
|
||||
@@ -90,11 +151,15 @@ const InternalTableContextProviders = ({
|
||||
objectMetadataItem: objectMetadataItem,
|
||||
recordTableId: objectMetadataItem.namePlural,
|
||||
viewBarId: 'view-bar',
|
||||
visibleTableColumns: visibleTableColumns,
|
||||
objectPermissions: getObjectPermissionsFromMapByObjectMetadataId({
|
||||
objectPermissionsByObjectMetadataId,
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
}),
|
||||
labelIdentifierFieldMetadataItem,
|
||||
recordFieldByFieldMetadataItemId,
|
||||
visibleRecordFields,
|
||||
fieldDefinitionByFieldMetadataItemId,
|
||||
fieldMetadataItemByFieldMetadataItemId,
|
||||
}}
|
||||
>
|
||||
<RecordTableBodyContextProvider
|
||||
|
||||
@@ -7,7 +7,7 @@ import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||
import { mockedApiKeys } from '~/testing/mock-data/api-keys';
|
||||
import {
|
||||
getCompaniesMock,
|
||||
getCompaniesRecordConnectionMock,
|
||||
getCompanyDuplicateMock,
|
||||
} from '~/testing/mock-data/companies';
|
||||
import { mockedClientConfig } from '~/testing/mock-data/config';
|
||||
@@ -37,7 +37,7 @@ import { mockedRemoteServers } from './mock-data/remote-servers';
|
||||
import { mockedViewFieldsData } from './mock-data/view-fields';
|
||||
|
||||
const peopleMock = getPeopleRecordConnectionMock();
|
||||
const companiesMock = getCompaniesMock();
|
||||
const companiesMock = getCompaniesRecordConnectionMock();
|
||||
const duplicateCompanyMock = getCompanyDuplicateMock();
|
||||
|
||||
export const metadataGraphql = graphql.link(
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { type Company } from '@/companies/types/Company';
|
||||
import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
|
||||
@@ -732,11 +734,20 @@ export const companiesQueryResult = {
|
||||
},
|
||||
};
|
||||
|
||||
const allMockedCompanyRecords = companiesQueryResult.companies.edges.map(
|
||||
(edge) => edge.node,
|
||||
);
|
||||
const allMockedCompanyRecords = getRecordsFromRecordConnection({
|
||||
recordConnection: companiesQueryResult.companies,
|
||||
}) as ObjectRecord[];
|
||||
|
||||
export const getCompaniesMock = () => {
|
||||
return [...allMockedCompanyRecords];
|
||||
return [...allMockedCompanyRecords] as Company[];
|
||||
};
|
||||
|
||||
export const getCompaniesRecordConnectionMock = () => {
|
||||
const companiesMock = companiesQueryResult.companies.edges.map(
|
||||
(edge) => edge.node,
|
||||
);
|
||||
|
||||
return companiesMock;
|
||||
};
|
||||
|
||||
export const getMockCompanyObjectMetadataItem = () => {
|
||||
@@ -744,9 +755,10 @@ export const getMockCompanyObjectMetadataItem = () => {
|
||||
|
||||
return companyObjectMetadataItem;
|
||||
};
|
||||
|
||||
export const getCompanyDuplicateMock = () => {
|
||||
return {
|
||||
...companiesQueryResult.companies.edges[0].node,
|
||||
...allMockedCompanyRecords[0],
|
||||
id: '8b40856a-2ec9-4c03-8bc0-c032c89e1824',
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getRecordFromRecordNode } from '@/object-record/cache/utils/getRecordFromRecordNode';
|
||||
import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
|
||||
import { type RecordGqlConnection } from '@/object-record/graphql/types/RecordGqlConnection';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
|
||||
import { type FieldMetadataType } from 'twenty-shared/types';
|
||||
import { getMockObjectMetadataItemOrThrow } from '~/testing/utils/getMockObjectMetadataItemOrThrow';
|
||||
|
||||
@@ -1747,9 +1748,9 @@ export const peopleQueryResult = {
|
||||
},
|
||||
} satisfies { people: RecordGqlConnection };
|
||||
|
||||
export const allMockPersonRecords = peopleQueryResult.people.edges.map((edge) =>
|
||||
getRecordFromRecordNode({ recordNode: edge.node }),
|
||||
);
|
||||
export const allMockPersonRecords = getRecordsFromRecordConnection({
|
||||
recordConnection: peopleQueryResult.people,
|
||||
}) as ObjectRecord[];
|
||||
|
||||
export const getPeopleRecordConnectionMock = () => {
|
||||
const peopleMock = peopleQueryResult.people.edges.map((edge) => edge.node);
|
||||
|
||||
@@ -34,6 +34,7 @@ export { capitalize } from './strings/capitalize';
|
||||
export type { StringPropertyKeys } from './trim-and-remove-duplicated-whitespaces-from-object-string-properties';
|
||||
export { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from './trim-and-remove-duplicated-whitespaces-from-object-string-properties';
|
||||
export { trimAndRemoveDuplicatedWhitespacesFromString } from './trim-and-remove-duplicated-whitespaces-from-string';
|
||||
export { throwIfNotDefined } from './typeguard/throwIfNotDefined';
|
||||
export { absoluteUrlSchema } from './url/absoluteUrlSchema';
|
||||
export { buildSignedPath } from './url/buildSignedPath';
|
||||
export { getAbsoluteUrl } from './url/getAbsoluteUrl';
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export const throwIfNotDefined = <T>(
|
||||
value: T,
|
||||
variableName: string
|
||||
): asserts value is NonNullable<T> => {
|
||||
if (value === null || value === undefined) {
|
||||
throw new Error(`Value must be defined for variable ${variableName}, this should not happen`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user