Removed useRecordTable (#13837)

This PR removes useRecordTable barrel-hook and instead use the pattern
that enforces one hook per function.

This allows to have a easier to maintain React code with small dedicated
hooks.

This PR also improves the usage of those hooks by cleaning some logic
that calls them.

We introduce a new hook : `useRecordIndexIdFromCurrentContextStore` that
factorizes logic that was duplicated to retrieve the `recordTableId`.

Introduced `useHandleColumnsChange`, that saves table columns into view,
this could be discussed but is it isolated with a TODO comment so ok for
now.

Also lowered the coverage because not enough to cover with test here.

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Lucas Bordeau
2025-08-12 10:52:27 +02:00
committed by GitHub
co-authored by Charles Bochet
parent 3a1c94147f
commit 6cc947ea01
50 changed files with 332 additions and 558 deletions
+1 -1
View File
@@ -60,7 +60,7 @@ const jestConfig: JestConfigWithTsJest = {
global: {
statements: 55,
lines: 54,
functions: 45,
functions: 44,
},
},
collectCoverageFrom: ['<rootDir>/src/**/*.ts'],
@@ -1,5 +1,4 @@
import { ActionModal } from '@/action-menu/actions/components/ActionModal';
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
@@ -9,13 +8,14 @@ import { DEFAULT_QUERY_PAGE_SIZE } from '@/object-record/constants/DefaultQueryP
import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords';
import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords';
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { t } from '@lingui/core/macro';
export const DeleteMultipleRecordsAction = () => {
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
const { recordIndexId, objectMetadataItem } =
useRecordIndexIdFromCurrentContextStore();
const contextStoreCurrentViewId = useRecoilComponentValue(
contextStoreCurrentViewIdComponentState,
@@ -25,12 +25,7 @@ export const DeleteMultipleRecordsAction = () => {
throw new Error('Current view ID is not defined');
}
const { resetTableRowSelection } = useRecordTable({
recordTableId: getRecordIndexIdFromObjectNamePluralAndViewId(
objectMetadataItem.namePlural,
contextStoreCurrentViewId,
),
});
const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId);
const { deleteManyRecords } = useDeleteManyRecords({
objectNameSingular: objectMetadataItem.nameSingular,
@@ -1,5 +1,4 @@
import { ActionModal } from '@/action-menu/actions/components/ActionModal';
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
@@ -10,12 +9,13 @@ import { type RecordGqlOperationFilter } from '@/object-record/graphql/types/Rec
import { useDestroyManyRecords } from '@/object-record/hooks/useDestroyManyRecords';
import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords';
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
export const DestroyMultipleRecordsAction = () => {
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
const { recordIndexId, objectMetadataItem } =
useRecordIndexIdFromCurrentContextStore();
const contextStoreCurrentViewId = useRecoilComponentValue(
contextStoreCurrentViewIdComponentState,
@@ -25,12 +25,7 @@ export const DestroyMultipleRecordsAction = () => {
throw new Error('Current view ID is not defined');
}
const { resetTableRowSelection } = useRecordTable({
recordTableId: getRecordIndexIdFromObjectNamePluralAndViewId(
objectMetadataItem.namePlural,
contextStoreCurrentViewId,
),
});
const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId);
const { destroyManyRecords } = useDestroyManyRecords({
objectNameSingular: objectMetadataItem.nameSingular,
@@ -1,5 +1,4 @@
import { ActionModal } from '@/action-menu/actions/components/ActionModal';
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
import { contextStoreAnyFieldFilterValueComponentState } from '@/context-store/states/contextStoreAnyFieldFilterValueComponentState';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
@@ -10,12 +9,13 @@ import { type RecordGqlOperationFilter } from '@/object-record/graphql/types/Rec
import { useLazyFetchAllRecords } from '@/object-record/hooks/useLazyFetchAllRecords';
import { useRestoreManyRecords } from '@/object-record/hooks/useRestoreManyRecords';
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
export const RestoreMultipleRecordsAction = () => {
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
const { recordIndexId, objectMetadataItem } =
useRecordIndexIdFromCurrentContextStore();
const contextStoreCurrentViewId = useRecoilComponentValue(
contextStoreCurrentViewIdComponentState,
@@ -25,12 +25,7 @@ export const RestoreMultipleRecordsAction = () => {
throw new Error('Current view ID is not defined');
}
const { resetTableRowSelection } = useRecordTable({
recordTableId: getRecordIndexIdFromObjectNamePluralAndViewId(
objectMetadataItem.namePlural,
contextStoreCurrentViewId,
),
});
const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId);
const { restoreManyRecords } = useRestoreManyRecords({
objectNameSingular: objectMetadataItem.nameSingular,
@@ -1,21 +1,20 @@
import { ActionModal } from '@/action-menu/actions/components/ActionModal';
import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow';
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite';
import { useFavorites } from '@/favorites/hooks/useFavorites';
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
export const DeleteSingleRecordAction = () => {
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
const { recordIndexId, objectMetadataItem } =
useRecordIndexIdFromCurrentContextStore();
const recordId = useSelectedRecordIdOrThrow();
const { resetTableRowSelection } = useRecordTable({
recordTableId: objectMetadataItem.namePlural,
});
const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId);
const { deleteOneRecord } = useDeleteOneRecord({
objectNameSingular: objectMetadataItem.nameSingular,
@@ -1,21 +1,20 @@
import { ActionModal } from '@/action-menu/actions/components/ActionModal';
import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow';
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
import { useDestroyOneRecord } from '@/object-record/hooks/useDestroyOneRecord';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
import { AppPath } from '@/types/AppPath';
import { useNavigateApp } from '~/hooks/useNavigateApp';
export const DestroySingleRecordAction = () => {
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
const { recordIndexId, objectMetadataItem } =
useRecordIndexIdFromCurrentContextStore();
const recordId = useSelectedRecordIdOrThrow();
const navigateApp = useNavigateApp();
const { resetTableRowSelection } = useRecordTable({
recordTableId: objectMetadataItem.namePlural,
});
const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId);
const { destroyOneRecord } = useDestroyOneRecord({
objectNameSingular: objectMetadataItem.nameSingular,
@@ -1,17 +1,16 @@
import { ActionModal } from '@/action-menu/actions/components/ActionModal';
import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow';
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
import { useRestoreManyRecords } from '@/object-record/hooks/useRestoreManyRecords';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
export const RestoreSingleRecordAction = () => {
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
const { recordIndexId, objectMetadataItem } =
useRecordIndexIdFromCurrentContextStore();
const recordId = useSelectedRecordIdOrThrow();
const { resetTableRowSelection } = useRecordTable({
recordTableId: objectMetadataItem.namePlural,
});
const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId);
const { restoreManyRecords } = useRestoreManyRecords({
objectNameSingular: objectMetadataItem.nameSingular,
@@ -78,7 +78,7 @@ export const PageChangeEffect = () => {
contextStoreCurrentViewId || '',
);
const resetTableSelections = useResetTableRowSelection(recordIndexId);
const { resetTableRowSelection } = useResetTableRowSelection(recordIndexId);
const { unfocusRecordTableRow } = useFocusedRecordTableRow(recordIndexId);
const { deactivateRecordTableRow } = useActiveRecordTableRow(recordIndexId);
@@ -124,7 +124,7 @@ export const PageChangeEffect = () => {
if (isLeavingRecordIndexPage) {
if (contextStoreCurrentViewType === ContextStoreViewType.Table) {
resetTableSelections();
resetTableRowSelection();
unfocusRecordTableRow();
deactivateRecordTableRow();
}
@@ -289,7 +289,7 @@ export const PageChangeEffect = () => {
location,
previousLocation,
contextStoreCurrentViewType,
resetTableSelections,
resetTableRowSelection,
unfocusRecordTableRow,
deactivateRecordTableRow,
resetRecordSelection,
@@ -12,7 +12,6 @@ import { peopleQueryResult } from '~/testing/mock-data/people';
const recordTableId = 'people';
const objectNameSingular = 'person';
const onColumnsChange = jest.fn();
const ObjectNamePluralSetter = ({ children }: { children: ReactNode }) => {
return <>{children}</>;
@@ -662,10 +661,7 @@ const Wrapper = ({ children }: { children: ReactNode }) => {
<ViewComponentInstanceContext.Provider
value={{ instanceId: 'instanceId' }}
>
<RecordTableComponentInstance
recordTableId={recordTableId}
onColumnsChange={onColumnsChange}
>
<RecordTableComponentInstance recordTableId={recordTableId}>
<RecordGroupContext.Provider value={{ recordGroupId: 'default' }}>
{children}
</RecordGroupContext.Provider>
@@ -52,7 +52,7 @@ const meta: Meta<typeof ObjectOptionsDropdownContent> = {
value={{ instanceId }}
>
<RecordTableComponentInstanceContext.Provider
value={{ instanceId, onColumnsChange: () => {} }}
value={{ instanceId }}
>
<ViewComponentInstanceContext.Provider value={{ instanceId }}>
<MemoryRouter
@@ -1,130 +0,0 @@
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useObjectOptionsForTable } from '@/object-record/object-options-dropdown/hooks/useObjectOptionsForTable';
import { type FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { type DropResult, type ResponderProvided } from '@hello-pangea/dnd';
import { renderHook } from '@testing-library/react';
import { act } from 'react';
import { RecoilRoot } from 'recoil';
describe('useObjectOptionsForTable', () => {
const fieldMetadataItems = [
{ id: 'id-1', name: 'field1' },
{ id: 'id-2', name: 'field2' },
{ id: 'id-3', name: 'field3' },
{ id: 'id-4', name: 'field4' },
{ id: 'id-5', name: 'field5' },
] as FieldMetadataItem[];
const columnDefinitionsWithMetadata = fieldMetadataItems.map(
(field, index) => ({
fieldMetadataId: field.id,
isVisible: true,
position: index + 1,
metadata: {
fieldName: field.name,
},
}),
);
const renderWithRecoil = () =>
renderHook(() => useObjectOptionsForTable('instance-id', 'object-id'), {
wrapper: ({ children }) => (
<RecordTableComponentInstanceContext.Provider
value={{ instanceId: 'instance-id', onColumnsChange: jest.fn() }}
>
<RecoilRoot
initializeState={({ set }) => {
set(
tableColumnsComponentState.atomFamily({
instanceId: 'instance-id',
}),
columnDefinitionsWithMetadata as ColumnDefinition<FieldMetadata>[],
);
set(objectMetadataItemsState, [
{
id: 'object-id',
nameSingular: 'Object',
namePlural: 'Objects',
fields: fieldMetadataItems,
readableFields: fieldMetadataItems,
updatableFields: fieldMetadataItems,
} as ObjectMetadataItem,
]);
}}
>
{children}
</RecoilRoot>
</RecordTableComponentInstanceContext.Provider>
),
});
it('reorders table columns correctly', () => {
const { result } = renderWithRecoil();
const dropResult = {
source: { droppableId: 'droppable', index: 2 },
destination: { droppableId: 'droppable', index: 3 },
draggableId: 'field2',
type: 'TYPE',
mode: 'FLUID',
reason: 'DROP',
combine: null,
} as DropResult;
const responderProvided = {
announce: jest.fn(),
} as ResponderProvided;
act(() => {
result.current.handleReorderColumns(dropResult, responderProvided);
});
expect(result.current.visibleTableColumns).toEqual([
{
fieldMetadataId: 'id-1',
isVisible: true,
metadata: {
fieldName: 'field1',
},
position: 0,
},
{
fieldMetadataId: 'id-3',
isVisible: true,
metadata: {
fieldName: 'field3',
},
position: 1,
},
{
fieldMetadataId: 'id-2',
isVisible: true,
metadata: {
fieldName: 'field2',
},
position: 2,
},
{
fieldMetadataId: 'id-4',
isVisible: true,
metadata: {
fieldName: 'field4',
},
position: 3,
},
{
fieldMetadataId: 'id-5',
isVisible: true,
metadata: {
fieldName: 'field5',
},
position: 4,
},
]);
});
});
@@ -25,7 +25,7 @@ export const useObjectOptionsForTable = (
);
const handleReorderColumns: OnDragEndResponder = useCallback(
(result) => {
async (result) => {
if (
!result.destination ||
result.destination.index === 1 ||
@@ -26,7 +26,6 @@ const meta: Meta = {
<RecordTableComponentInstanceContext.Provider
value={{
instanceId,
onColumnsChange: () => {},
}}
>
<RecordIndexContextProvider
@@ -2,9 +2,6 @@ import { RecordIndexContextProvider } from '@/object-record/record-index/context
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { getActionMenuIdFromRecordIndexId } from '@/action-menu/utils/getActionMenuIdFromRecordIndexId';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { lastShowPageRecordIdState } from '@/object-record/record-field/states/lastShowPageRecordId';
@@ -15,12 +12,11 @@ import { RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect } from '@
import { RecordIndexLoadBaseOnContextStoreEffect } from '@/object-record/record-index/components/RecordIndexLoadBaseOnContextStoreEffect';
import { RecordIndexPageHeader } from '@/object-record/record-index/components/RecordIndexPageHeader';
import { useHandleIndexIdentifierClick } from '@/object-record/record-index/hooks/useHandleIndexIdentifierClick';
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext';
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
import { PageBody } from '@/ui/layout/page/components/PageBody';
import { RECORD_INDEX_DRAG_SELECT_BOUNDARY_CLASS } from '@/ui/utilities/drag-select/constants/RecordIndecDragSelectBoundaryClass';
import { PageTitle } from '@/ui/utilities/page-title/components/PageTitle';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
import styled from '@emotion/styled';
import { useRecoilCallback } from 'recoil';
@@ -33,17 +29,8 @@ const StyledIndexContainer = styled.div`
`;
export const RecordIndexContainerGater = () => {
const contextStoreCurrentViewId = useRecoilComponentValue(
contextStoreCurrentViewIdComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
const recordIndexId = getRecordIndexIdFromObjectNamePluralAndViewId(
objectMetadataItem.namePlural,
contextStoreCurrentViewId || '',
);
const { recordIndexId, objectMetadataItem } =
useRecordIndexIdFromCurrentContextStore();
const handleIndexRecordsLoaded = useRecoilCallback(
({ set }) =>
@@ -3,8 +3,7 @@ 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 { useHandleToggleColumnSort } from '@/object-record/record-index/hooks/useHandleToggleColumnSort';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
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';
@@ -15,9 +14,8 @@ import { isDefined } from 'twenty-shared/utils';
export const RecordIndexTableContainerEffect = () => {
const { recordIndexId, objectNameSingular } = useRecordIndexContextOrThrow();
const { setAvailableTableColumns, setOnToggleColumnSort } = useRecordTable({
recordTableId: recordIndexId,
});
const { setAvailableTableColumns } =
useSetAvailableTableColumns(recordIndexId);
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
@@ -30,19 +28,8 @@ export const RecordIndexTableContainerEffect = () => {
setAvailableTableColumns(columnDefinitions);
}, [columnDefinitions, setAvailableTableColumns]);
const handleToggleColumnSort = useHandleToggleColumnSort({
objectNameSingular,
});
const { currentView } = useGetCurrentViewOnly();
useEffect(() => {
setOnToggleColumnSort(
() => (fieldMetadataId: string) =>
handleToggleColumnSort(fieldMetadataId),
);
}, [setOnToggleColumnSort, handleToggleColumnSort]);
const setViewFieldAggregateOperation = useRecoilCallback(
({ set, snapshot }) =>
(viewField: ViewField) => {
@@ -0,0 +1,48 @@
import { useRecordIndexIdFromCurrentContextStore } from '@/object-record/record-index/hooks/useRecordIndexIdFromCurrentContextStore';
import { renderHook } from '@testing-library/react';
import { JestObjectMetadataItemSetter } from '~/testing/jest/JestObjectMetadataItemSetter';
import { getJestMetadataAndApolloMocksAndActionMenuWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksAndActionMenuWrapper';
const testComponentInstanceId = 'test';
const testCurrentViewId = 'view-id';
const testObjectMetadataItemNameSingular = 'person';
const testObjectMetadataItemNamePlural = 'people';
const Wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({
apolloMocks: [],
componentInstanceId: testComponentInstanceId,
contextStoreCurrentObjectMetadataNameSingular:
testObjectMetadataItemNameSingular,
contextStoreCurrentViewId: testCurrentViewId,
});
describe('useRecordIndexIdFromCurrentContextStore', () => {
it('works as expected with context', async () => {
const { result } = renderHook(
() => {
const { objectMetadataItem, recordIndexId } =
useRecordIndexIdFromCurrentContextStore();
return {
objectMetadataItem,
recordIndexId,
};
},
{
wrapper: ({ children }) => {
return (
<Wrapper>
<JestObjectMetadataItemSetter>
{children}
</JestObjectMetadataItemSetter>
</Wrapper>
);
},
},
);
expect(result.current.recordIndexId).toBe(
`${testObjectMetadataItemNamePlural}-${testCurrentViewId}`,
);
});
});
@@ -1,21 +1,21 @@
import { useCallback } from 'react';
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
import { useUpsertRecordSort } from '@/object-record/record-sort/hooks/useUpsertRecordSort';
import { type RecordSort } from '@/object-record/record-sort/types/RecordSort';
import { v4 } from 'uuid';
import { isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
type UseHandleToggleColumnSortProps = {
objectNameSingular: string;
objectMetadataItemId: string;
};
export const useHandleToggleColumnSort = ({
objectNameSingular,
objectMetadataItemId,
}: UseHandleToggleColumnSortProps) => {
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
const { objectMetadataItem } = useObjectMetadataItemById({
objectId: objectMetadataItemId,
});
const { columnDefinitions } =
@@ -0,0 +1,22 @@
import { useContextStoreObjectMetadataItemOrThrow } from '@/context-store/hooks/useContextStoreObjectMetadataItemOrThrow';
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
export const useRecordIndexIdFromCurrentContextStore = () => {
const contextStoreCurrentViewId = useRecoilComponentValue(
contextStoreCurrentViewIdComponentState,
);
const { objectMetadataItem } = useContextStoreObjectMetadataItemOrThrow();
const recordIndexId = getRecordIndexIdFromObjectNamePluralAndViewId(
objectMetadataItem.namePlural,
contextStoreCurrentViewId || '',
);
return {
objectMetadataItem,
recordIndexId,
};
};
@@ -11,7 +11,7 @@ import { RecordTableScrollToFocusedCellEffect } from '@/object-record/record-tab
import { RecordTableScrollToFocusedRowEffect } from '@/object-record/record-table/components/RecordTableScrollToFocusedRowEffect';
import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
@@ -45,9 +45,7 @@ export const RecordTable = () => {
recordTableId,
);
const { resetTableRowSelection, setRowSelected } = useRecordTable({
recordTableId,
});
const { resetTableRowSelection } = useResetTableRowSelection(recordTableId);
const recordTableIsEmpty =
!isRecordTableInitialLoading && allRecordIds.length === 0;
@@ -84,7 +82,6 @@ export const RecordTable = () => {
tableBodyRef={tableBodyRef}
handleDragSelectionStart={handleDragSelectionStart}
handleDragSelectionEnd={handleDragSelectionEnd}
setRowSelected={setRowSelected}
hasRecordGroups={hasRecordGroups}
recordTableId={recordTableId}
/>
@@ -1,30 +1,22 @@
import { type ReactNode } from 'react';
import { type FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { RecordTableComponentInstanceEffect } from '@/object-record/record-table/components/RecordTableComponentInstanceEffect';
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
type RecordTableComponentInstanceProps = {
children: ReactNode;
recordTableId: string;
onColumnsChange: (columns: ColumnDefinition<FieldMetadata>[]) => void;
};
export const RecordTableComponentInstance = ({
children,
recordTableId,
onColumnsChange,
}: RecordTableComponentInstanceProps) => {
return (
<RecordTableComponentInstanceContext.Provider
value={{
instanceId: recordTableId,
onColumnsChange,
}}
>
<RecordTableComponentInstanceEffect onColumnsChange={onColumnsChange} />
{children}
</RecordTableComponentInstanceContext.Provider>
);
@@ -1,21 +0,0 @@
import { useEffect } from 'react';
import { type FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
type RecordTableComponentInstanceEffectProps = {
onColumnsChange: (columns: ColumnDefinition<FieldMetadata>[]) => void;
};
export const RecordTableComponentInstanceEffect = ({
onColumnsChange,
}: RecordTableComponentInstanceEffectProps) => {
const { setOnColumnsChange } = useRecordTable();
useEffect(() => {
setOnColumnsChange(() => onColumnsChange);
}, [onColumnsChange, setOnColumnsChange]);
return <></>;
};
@@ -4,10 +4,13 @@ import { StyledTable } from '@/object-record/record-table/components/RecordTable
import { RecordTableNoRecordGroupBody } from '@/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBody';
import { RecordTableRecordGroupsBody } from '@/object-record/record-table/record-table-body/components/RecordTableRecordGroupsBody';
import { RecordTableHeader } from '@/object-record/record-table/record-table-header/components/RecordTableHeader';
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
import { RECORD_INDEX_DRAG_SELECT_BOUNDARY_CLASS } from '@/ui/utilities/drag-select/constants/RecordIndecDragSelectBoundaryClass';
import { useRecoilComponentFamilyCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyCallbackState';
import styled from '@emotion/styled';
import { useRef, useState } from 'react';
import { useRecoilCallback } from 'recoil';
const StyledTableWithPointerEvents = styled(StyledTable)<{
isDragging: boolean;
@@ -28,7 +31,6 @@ export interface RecordTableContentProps {
tableBodyRef: React.RefObject<HTMLTableElement>;
handleDragSelectionStart: () => void;
handleDragSelectionEnd: () => void;
setRowSelected: (rowId: string, selected: boolean) => void;
hasRecordGroups: boolean;
recordTableId: string;
}
@@ -37,7 +39,6 @@ export const RecordTableContent = ({
tableBodyRef,
handleDragSelectionStart,
handleDragSelectionEnd,
setRowSelected,
hasRecordGroups,
recordTableId,
}: RecordTableContentProps) => {
@@ -54,6 +55,17 @@ export const RecordTableContent = ({
handleDragSelectionEnd();
};
const isRowSelectedCallbackFamilyState =
useRecoilComponentFamilyCallbackState(isRowSelectedComponentFamilyState);
const handleDragSelectionChange = useRecoilCallback(
({ set }) =>
(rowId: string, selected: boolean) => {
set(isRowSelectedCallbackFamilyState(rowId), selected);
},
[isRowSelectedCallbackFamilyState],
);
return (
<StyledTableContainer ref={containerRef}>
<StyledTableWithPointerEvents ref={tableBodyRef} isDragging={isDragging}>
@@ -69,7 +81,7 @@ export const RecordTableContent = ({
<DragSelect
selectableItemsContainerRef={containerRef}
onDragSelectionStart={handleDragStart}
onDragSelectionChange={setRowSelected}
onDragSelectionChange={handleDragSelectionChange}
onDragSelectionEnd={handleDragEnd}
scrollWrapperComponentInstanceId={`record-table-scroll-${recordTableId}`}
selectionBoundaryClass={RECORD_INDEX_DRAG_SELECT_BOUNDARY_CLASS}
@@ -1,19 +1,14 @@
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { type FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { RecordTable } from '@/object-record/record-table/components/RecordTable';
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider';
import { EntityDeleteContext } from '@/object-record/record-table/contexts/EntityDeleteHookContext';
import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { useSelectAllRows } from '@/object-record/record-table/hooks/internal/useSelectAllRows';
import { PageFocusId } from '@/types/PageFocusId';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { useSaveCurrentViewFields } from '@/views/hooks/useSaveCurrentViewFields';
import { mapColumnDefinitionsToViewFields } from '@/views/utils/mapColumnDefinitionToViewField';
import styled from '@emotion/styled';
import { useRecoilCallback } from 'recoil';
import { RecordUpdateContext } from '../contexts/EntityUpdateMutationHookContext';
import { useRecordTable } from '../hooks/useRecordTable';
const StyledTableContainer = styled.div`
display: flex;
@@ -36,12 +31,9 @@ export const RecordTableWithWrappers = ({
recordTableId,
viewBarId,
}: RecordTableWithWrappersProps) => {
const { selectAllRows, setHasUserSelectedAllRows } = useRecordTable({
recordTableId,
});
const { selectAllRows } = useSelectAllRows(recordTableId);
const handleSelectAllRows = () => {
setHasUserSelectedAllRows(true);
selectAllRows();
};
@@ -55,26 +47,10 @@ export const RecordTableWithWrappers = ({
},
});
const { saveViewFields } = useSaveCurrentViewFields();
const { deleteOneRecord } = useDeleteOneRecord({ objectNameSingular });
const handleColumnsChange = useRecoilCallback(
() => (columns) => {
saveViewFields(
mapColumnDefinitionsToViewFields(
columns as ColumnDefinition<FieldMetadata>[],
),
);
},
[saveViewFields],
);
return (
<RecordTableComponentInstance
recordTableId={recordTableId}
onColumnsChange={handleColumnsChange}
>
<RecordTableComponentInstance recordTableId={recordTableId}>
<RecordTableContextProvider
recordTableId={recordTableId}
viewBarId={viewBarId}
@@ -75,10 +75,7 @@ const meta: Meta = {
},
}}
>
<RecordTableComponentInstance
recordTableId="asd"
onColumnsChange={() => {}}
>
<RecordTableComponentInstance recordTableId="asd">
<RecordTableBodyContextProvider
value={{
onOpenTableCell: () => {},
@@ -14,9 +14,7 @@ export const useLeaveTableFocus = (recordTableId?: string) => {
recordTableId,
);
const resetTableRowSelection = useResetTableRowSelection(
recordTableIdFromContext,
);
const { resetTableRowSelection } = useResetTableRowSelection();
const setRecordTableHoverPosition = useSetRecoilComponentState(
recordTableHoverPositionComponentState,
@@ -41,7 +41,7 @@ export const useResetTableRowSelection = (recordTableId?: string) => {
const { closeDropdown } = useCloseDropdown();
return useRecoilCallback(
const resetTableRowSelection = useRecoilCallback(
({ set, snapshot }) =>
() => {
const allRecordIds = getSnapshotValue(
@@ -72,4 +72,8 @@ export const useResetTableRowSelection = (recordTableId?: string) => {
recordTableIdFromContext,
],
);
return {
resetTableRowSelection,
};
};
@@ -2,16 +2,23 @@ import { useRecoilCallback } from 'recoil';
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
import { hasUserSelectedAllRowsComponentState } from '@/object-record/record-table/record-table-row/states/hasUserSelectedAllRowsFamilyState';
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
import { allRowsSelectedStatusComponentSelector } from '@/object-record/record-table/states/selectors/allRowsSelectedStatusComponentSelector';
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
export const useSelectAllRows = (recordTableId?: string) => {
const hasUserSelectedAllRowsCallbackState = useRecoilComponentCallbackState(
hasUserSelectedAllRowsComponentState,
recordTableId,
);
const allRowsSelectedStatusSelector = useRecoilComponentCallbackState(
allRowsSelectedStatusComponentSelector,
recordTableId,
);
const isRowSelectedFamilyState = useRecoilComponentCallbackState(
isRowSelectedComponentFamilyState,
recordTableId,
@@ -21,7 +28,7 @@ export const useSelectAllRows = (recordTableId?: string) => {
recordTableId,
);
const resetTableRowSelection = useResetTableRowSelection(recordTableId);
const { resetTableRowSelection } = useResetTableRowSelection(recordTableId);
const selectAllRows = useRecoilCallback(
({ set, snapshot }) =>
@@ -47,12 +54,15 @@ export const useSelectAllRows = (recordTableId?: string) => {
set(isRowSelectedFamilyState(recordId), isSelected);
}
set(hasUserSelectedAllRowsCallbackState, true);
},
[
allRowsSelectedStatusSelector,
recordIndexAllRecordIdsSelector,
resetTableRowSelection,
isRowSelectedFamilyState,
hasUserSelectedAllRowsCallbackState,
],
);
@@ -1,19 +0,0 @@
import { useRecoilCallback } from 'recoil';
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
export const useSetRowSelectedState = (recordTableId?: string) => {
const isRowSelectedFamilyState = useRecoilComponentCallbackState(
isRowSelectedComponentFamilyState,
recordTableId,
);
return useRecoilCallback(
({ set }) =>
(rowId: string, selected: boolean) => {
set(isRowSelectedFamilyState(rowId), selected);
},
[isRowSelectedFamilyState],
);
};
@@ -0,0 +1,29 @@
import { type FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { useSaveColumnsToView } from '@/object-record/record-table/hooks/useSaveColumnsToView';
import { useSetTableColumns } from '@/object-record/record-table/hooks/useSetTableColumns';
import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
// 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.
export const useHandleColumnsChange = () => {
const { saveColumnsToView } = useSaveColumnsToView();
const { setTableColumns } = useSetTableColumns();
const handleColumnsChange = async ({
columns,
recordTableId,
objectMetadataId,
}: {
columns: ColumnDefinition<FieldMetadata>[];
recordTableId: string;
objectMetadataId: string;
}) => {
setTableColumns(columns, recordTableId, objectMetadataId);
await saveColumnsToView(columns);
};
return {
handleColumnsChange,
};
};
@@ -1,109 +0,0 @@
import { useRecoilCallback } from 'recoil';
import { type FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { useSetHasUserSelectedAllRows } from '@/object-record/record-table/hooks/internal/useSetAllRowSelectedState';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { type ColumnDefinition } from '../types/ColumnDefinition';
import { availableTableColumnsComponentState } from '@/object-record/record-table/states/availableTableColumnsComponentState';
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
import { onColumnsChangeComponentState } from '@/object-record/record-table/states/onColumnsChangeComponentState';
import { onToggleColumnSortComponentState } from '@/object-record/record-table/states/onToggleColumnSortComponentState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
import { useLeaveTableFocus } from './internal/useLeaveTableFocus';
import { useResetTableRowSelection } from './internal/useResetTableRowSelection';
import { useSelectAllRows } from './internal/useSelectAllRows';
import { useSetRowSelectedState } from './internal/useSetRowSelectedState';
type useRecordTableProps = {
recordTableId?: string;
};
export const useRecordTable = (props?: useRecordTableProps) => {
const recordTableId = useAvailableComponentInstanceIdOrThrow(
RecordTableComponentInstanceContext,
props?.recordTableId,
);
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],
);
const setOnColumnsChange = useSetRecoilComponentState(
onColumnsChangeComponentState,
recordTableId,
);
const setOnToggleColumnSort = useSetRecoilComponentState(
onToggleColumnSortComponentState,
recordTableId,
);
const setIsRecordTableInitialLoading = useSetRecoilComponentState(
isRecordTableInitialLoadingComponentState,
recordTableId,
);
const onColumnsChangeState = useRecoilComponentCallbackState(
onColumnsChangeComponentState,
recordTableId,
);
const onColumnsChange = useRecoilCallback(
({ snapshot }) =>
(columns: ColumnDefinition<FieldMetadata>[]) => {
const onColumnsChange = getSnapshotValue(
snapshot,
onColumnsChangeState,
);
onColumnsChange?.(columns);
},
[onColumnsChangeState],
);
const leaveTableFocus = useLeaveTableFocus(recordTableId);
const setRowSelected = useSetRowSelectedState(recordTableId);
const setHasUserSelectedAllRows = useSetHasUserSelectedAllRows(recordTableId);
const resetTableRowSelection = useResetTableRowSelection(recordTableId);
const { selectAllRows } = useSelectAllRows(recordTableId);
return {
onColumnsChange,
setAvailableTableColumns,
leaveTableFocus,
setRowSelected,
resetTableRowSelection,
selectAllRows,
setOnColumnsChange,
setIsRecordTableInitialLoading,
setHasUserSelectedAllRows,
setOnToggleColumnSort,
};
};
@@ -0,0 +1,22 @@
import { type FieldMetadata } from '@/object-record/record-field/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 };
};
@@ -0,0 +1,43 @@
import { type FieldMetadata } from '@/object-record/record-field/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,
};
};
@@ -1,64 +1,44 @@
import { useCallback } from 'react';
import { type FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { useUnfocusRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useUnfocusRecordTableCell';
import { useMoveViewColumns } from '@/views/hooks/useMoveViewColumns';
import { useSetTableColumns } from '@/object-record/record-table/hooks/useSetTableColumns';
import { useHandleColumnsChange } from '@/object-record/record-table/hooks/useHandleColumnsChange';
import { availableTableColumnsComponentState } from '@/object-record/record-table/states/availableTableColumnsComponentState';
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { type ColumnDefinition } from '../types/ColumnDefinition';
type useRecordTableProps = {
recordTableId?: string;
recordTableId: string;
objectMetadataId: string;
};
export const useTableColumns = (props: useRecordTableProps) => {
const { onColumnsChange } = useRecordTable({
recordTableId: props?.recordTableId,
});
const { setTableColumns } = useSetTableColumns();
export const useTableColumns = ({
objectMetadataId,
recordTableId,
}: useRecordTableProps) => {
const availableTableColumns = useRecoilComponentValue(
availableTableColumnsComponentState,
props?.recordTableId,
recordTableId,
);
const tableColumns = useRecoilComponentValue(
tableColumnsComponentState,
props?.recordTableId,
recordTableId,
);
const visibleTableColumns = useRecoilComponentValue(
visibleTableColumnsComponentSelector,
props?.recordTableId,
recordTableId,
);
const { handleColumnMove } = useMoveViewColumns();
const { unfocusRecordTableCell } = useUnfocusRecordTableCell(
props?.recordTableId,
);
const { unfocusRecordTableCell } = useUnfocusRecordTableCell(recordTableId);
const instanceId = useAvailableComponentInstanceIdOrThrow(
RecordTableComponentInstanceContext,
props?.recordTableId,
);
const handleColumnsChange = useCallback(
async (columns: ColumnDefinition<FieldMetadata>[]) => {
setTableColumns(columns, instanceId, props.objectMetadataId);
await onColumnsChange?.(columns);
},
[setTableColumns, instanceId, onColumnsChange, props.objectMetadataId],
);
const { handleColumnsChange } = useHandleColumnsChange();
const handleColumnVisibilityChange = useCallback(
async (
@@ -88,7 +68,11 @@ export const useTableColumns = (props: useRecordTableProps) => {
{ ...newColumn, isVisible: true, position: lastPosition + 1 },
];
await handleColumnsChange(nextColumns);
await handleColumnsChange({
columns: nextColumns,
objectMetadataId,
recordTableId,
});
} else {
const nextColumns = visibleTableColumns.map((previousColumn) =>
previousColumn.fieldMetadataId === viewField.fieldMetadataId
@@ -96,7 +80,11 @@ export const useTableColumns = (props: useRecordTableProps) => {
: previousColumn,
);
await handleColumnsChange(nextColumns);
await handleColumnsChange({
columns: nextColumns,
objectMetadataId,
recordTableId,
});
}
},
[
@@ -104,6 +92,8 @@ export const useTableColumns = (props: useRecordTableProps) => {
availableTableColumns,
handleColumnsChange,
visibleTableColumns,
objectMetadataId,
recordTableId,
],
);
@@ -125,13 +115,19 @@ export const useTableColumns = (props: useRecordTableProps) => {
visibleTableColumns,
);
await handleColumnsChange(columns);
await handleColumnsChange({
columns,
objectMetadataId,
recordTableId,
});
},
[
unfocusRecordTableCell,
visibleTableColumns,
handleColumnMove,
handleColumnsChange,
objectMetadataId,
recordTableId,
],
);
@@ -142,9 +138,13 @@ export const useTableColumns = (props: useRecordTableProps) => {
position: index,
}));
await handleColumnsChange(updatedColumns);
await handleColumnsChange({
columns: updatedColumns,
objectMetadataId,
recordTableId,
});
},
[handleColumnsChange],
[handleColumnsChange, objectMetadataId, recordTableId],
);
return {
@@ -1,18 +1,13 @@
import { Key } from 'ts-key-enum';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
import { isAtLeastOneTableRowSelectedSelector } from '@/object-record/record-table/record-table-row/states/isAtLeastOneTableRowSelectedSelector';
import { PageFocusId } from '@/types/PageFocusId';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
export const RecordTableBodyEscapeHotkeyEffect = () => {
const { recordTableId } = useRecordTableContextOrThrow();
const { resetTableRowSelection } = useRecordTable({
recordTableId,
});
const { resetTableRowSelection } = useResetTableRowSelection();
const isAtLeastOneRecordSelected = useRecoilComponentValue(
isAtLeastOneTableRowSelectedSelector,
@@ -7,9 +7,8 @@ import { useIsFieldClearable } from '@/object-record/record-field/hooks/useIsFie
import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly';
import { useToggleEditOnlyInput } from '@/object-record/record-field/hooks/useToggleEditOnlyInput';
import { useRecordTableBodyContextOrThrow } from '@/object-record/record-table/contexts/RecordTableBodyContext';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useSelectAllRows } from '@/object-record/record-table/hooks/internal/useSelectAllRows';
import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { useCurrentlyFocusedRecordTableCellFocusId } from '@/object-record/record-table/record-table-cell/hooks/useCurrentlyFocusedRecordTableCellFocusId';
import { useOpenRecordTableCellFromCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellFromCell';
import { useListenToSidePanelOpening } from '@/ui/layout/right-drawer/hooks/useListenToSidePanelOpening';
@@ -68,10 +67,8 @@ export const RecordTableCellHotkeysEffect = () => {
}
};
const { recordTableId } = useRecordTableContextOrThrow();
const { restoreRecordTableRowFocusFromCellPosition } =
useFocusedRecordTableRow(recordTableId);
useFocusedRecordTableRow();
const handleEscape = () => {
restoreRecordTableRowFocusFromCellPosition();
@@ -110,12 +107,9 @@ export const RecordTableCellHotkeysEffect = () => {
},
});
const { selectAllRows, setHasUserSelectedAllRows } = useRecordTable({
recordTableId,
});
const { selectAllRows } = useSelectAllRows();
const handleSelectAllRows = () => {
setHasUserSelectedAllRows(true);
selectAllRows();
};
@@ -16,10 +16,7 @@ import { recordTableHoverPositionComponentState } from '@/object-record/record-t
const Wrapper = ({ children }: { children: React.ReactNode }) => (
<RecoilRoot>
<RecordTableComponentInstance
recordTableId="test-record-table-instance-id"
onColumnsChange={jest.fn()}
>
<RecordTableComponentInstance recordTableId="test-record-table-instance-id">
<RecordTableRowContextProvider value={recordTableRowContextValue}>
<RecordTableRowDraggableContextProvider
value={recordTableRowDraggableContextValue}
@@ -35,10 +35,7 @@ const Wrapper = ({ children }: { children: React.ReactNode }) => (
);
}}
>
<RecordTableComponentInstance
recordTableId="test-table-id"
onColumnsChange={jest.fn()}
>
<RecordTableComponentInstance recordTableId="test-table-id">
{children}
</RecordTableComponentInstance>
</RecoilRoot>
@@ -21,7 +21,6 @@ import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
const onColumnsChange = jest.fn();
const recordTableId = 'record-table-id';
const Wrapper = ({ children }: { children: React.ReactNode }) => (
@@ -30,10 +29,7 @@ const Wrapper = ({ children }: { children: React.ReactNode }) => (
snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems);
}}
>
<RecordTableComponentInstance
recordTableId={recordTableId}
onColumnsChange={onColumnsChange}
>
<RecordTableComponentInstance recordTableId={recordTableId}>
<RecordTableContextProvider
recordTableId={recordTableId}
viewBarId="viewBarId"
@@ -21,7 +21,6 @@ import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
const onColumnsChange = jest.fn();
const recordTableId = 'record-table-id';
const Wrapper = ({ children }: { children: React.ReactNode }) => (
@@ -30,10 +29,7 @@ const Wrapper = ({ children }: { children: React.ReactNode }) => (
snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems);
}}
>
<RecordTableComponentInstance
recordTableId={recordTableId}
onColumnsChange={onColumnsChange}
>
<RecordTableComponentInstance recordTableId={recordTableId}>
<RecordTableContextProvider
recordTableId={recordTableId}
viewBarId="viewBarId"
@@ -2,8 +2,9 @@ import { type FieldMetadata } from '@/object-record/record-field/types/FieldMeta
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { useHandleToggleColumnSort } from '@/object-record/record-index/hooks/useHandleToggleColumnSort';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useOpenRecordFilterChipFromTableHeader } from '@/object-record/record-table/record-table-header/hooks/useOpenRecordFilterChipFromTableHeader';
import { onToggleColumnSortComponentState } from '@/object-record/record-table/states/onToggleColumnSortComponentState';
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';
@@ -53,8 +54,10 @@ export const RecordTableColumnHeadDropdownMenu = ({
const canMoveRight =
column.fieldMetadataId !== lastVisibleColumn?.fieldMetadataId && canMove;
const { recordTableId } = useRecordTableContextOrThrow();
const { handleColumnVisibilityChange, handleMoveTableColumn } =
useTableColumns({ objectMetadataId });
useTableColumns({ objectMetadataId, recordTableId });
const dropdownId = column.fieldMetadataId + '-header';
@@ -86,14 +89,14 @@ export const RecordTableColumnHeadDropdownMenu = ({
handleColumnVisibilityChange(column);
};
const onToggleColumnSort = useRecoilComponentValue(
onToggleColumnSortComponentState,
);
const handleToggleColumnSort = useHandleToggleColumnSort({
objectMetadataItemId: objectMetadataId,
});
const handleSortClick = () => {
closeDropdownAndToggleScroll();
onToggleColumnSort?.(column.fieldMetadataId);
handleToggleColumnSort(column.fieldMetadataId);
};
const { openRecordFilterChipFromTableHeader } =
@@ -114,7 +114,7 @@ type RecordTableHeaderCellProps = {
export const RecordTableHeaderCell = ({
column,
}: RecordTableHeaderCellProps) => {
const { objectMetadataItem, objectPermissions } =
const { objectMetadataItem, objectPermissions, recordTableId } =
useRecordTableContextOrThrow();
const resizeFieldOffsetState = useRecoilComponentCallbackState(
@@ -139,6 +139,7 @@ export const RecordTableHeaderCell = ({
const { handleColumnsChange } = useTableColumns({
objectMetadataId: objectMetadataItem.id,
recordTableId,
});
const handleResizeHandlerStart = useCallback<PointerEventListener>(
@@ -186,7 +187,11 @@ export const RecordTableHeaderCell = ({
: column,
);
await handleColumnsChange(nextColumns);
await handleColumnsChange({
columns: nextColumns,
objectMetadataId: objectMetadataItem.id,
recordTableId,
});
}
},
[
@@ -196,6 +201,8 @@ export const RecordTableHeaderCell = ({
setResizedFieldKey,
tableColumns,
handleColumnsChange,
objectMetadataItem,
recordTableId,
],
);
@@ -2,7 +2,8 @@ import styled from '@emotion/styled';
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
import { useSelectAllRows } from '@/object-record/record-table/hooks/internal/useSelectAllRows';
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState';
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
@@ -37,8 +38,11 @@ export const RecordTableHeaderCheckboxColumn = () => {
const allRowsSelectedStatus = useRecoilComponentValue(
allRowsSelectedStatusComponentSelector,
);
const { selectAllRows, resetTableRowSelection, setHasUserSelectedAllRows } =
useRecordTable();
const { selectAllRows } = useSelectAllRows();
const { resetTableRowSelection } = useResetTableRowSelection();
const checked =
allRowsSelectedStatus === 'all' || allRowsSelectedStatus === 'some';
const indeterminate = allRowsSelectedStatus === 'some';
@@ -60,10 +64,8 @@ export const RecordTableHeaderCheckboxColumn = () => {
const onChange = () => {
if (checked) {
setHasUserSelectedAllRows(false);
resetTableRowSelection();
} else {
setHasUserSelectedAllRows(true);
selectAllRows();
}
};
@@ -21,7 +21,7 @@ import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
export const RecordTableHeaderPlusButtonContent = () => {
const { t } = useLingui();
const { objectMetadataItem } = useRecordTableContextOrThrow();
const { objectMetadataItem, recordTableId } = useRecordTableContextOrThrow();
const { closeDropdown } = useCloseDropdown();
@@ -32,6 +32,7 @@ export const RecordTableHeaderPlusButtonContent = () => {
const { getIcon } = useIcons();
const { handleColumnVisibilityChange } = useTableColumns({
objectMetadataId: objectMetadataItem.id,
recordTableId,
});
const handleAddColumn = useCallback(
@@ -1,9 +1,10 @@
import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
import { useSelectAllRows } from '@/object-record/record-table/hooks/internal/useSelectAllRows';
import { useActiveRecordTableRow } from '@/object-record/record-table/hooks/useActiveRecordTableRow';
import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { useFocusRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useFocusRecordTableCell';
import { getRecordTableCellFocusId } from '@/object-record/record-table/record-table-cell/utils/getRecordTableCellFocusId';
import { useSetCurrentRowSelected } from '@/object-record/record-table/record-table-row/hooks/useSetCurrentRowSelected';
@@ -81,9 +82,7 @@ export const useRecordTableRowHotkeys = (focusId: string) => {
});
};
const { resetTableRowSelection } = useRecordTable({
recordTableId,
});
const { resetTableRowSelection } = useResetTableRowSelection(recordTableId);
const { unfocusRecordTableRow } = useFocusedRecordTableRow(recordTableId);
@@ -133,12 +132,9 @@ export const useRecordTableRowHotkeys = (focusId: string) => {
dependencies: [handleEscape],
});
const { selectAllRows, setHasUserSelectedAllRows } = useRecordTable({
recordTableId,
});
const { selectAllRows } = useSelectAllRows();
const handleSelectAllRows = () => {
setHasUserSelectedAllRows(true);
selectAllRows();
};
@@ -1,12 +1,4 @@
import { type FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { type ComponentStateKey } from '@/ui/utilities/state/component-state/types/ComponentStateKey';
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';
import { type ColumnDefinition } from '../../types/ColumnDefinition';
type RecordTableComponentInstanceContextProps = ComponentStateKey & {
onColumnsChange: (columns: ColumnDefinition<FieldMetadata>[]) => void;
};
export const RecordTableComponentInstanceContext =
createComponentInstanceContext<RecordTableComponentInstanceContextProps>();
createComponentInstanceContext();
@@ -1,12 +0,0 @@
import { type FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
import { type ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
export const onColumnsChangeComponentState = createComponentState<
((columns: ColumnDefinition<FieldMetadata>[]) => void) | undefined
>({
key: 'onColumnsChangeComponentState',
defaultValue: undefined,
componentInstanceContext: RecordTableComponentInstanceContext,
});
@@ -1,10 +0,0 @@
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
export const onToggleColumnSortComponentState = createComponentState<
((fieldMetadataId: string) => void) | undefined
>({
key: 'onToggleColumnSortComponentState',
defaultValue: undefined,
componentInstanceContext: RecordTableComponentInstanceContext,
});
@@ -4,7 +4,7 @@ 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 { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { useSetAvailableTableColumns } from '@/object-record/record-table/hooks/useSetAvailableTableColumns';
import { useSetTableColumns } from '@/object-record/record-table/hooks/useSetTableColumns';
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';
@@ -31,9 +31,8 @@ export const SignInBackgroundMockContainerEffect = ({
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const { setAvailableTableColumns } = useRecordTable({
recordTableId,
});
const { setAvailableTableColumns } =
useSetAvailableTableColumns(recordTableId);
const { setTableColumns } = useSetTableColumns();
@@ -121,7 +121,6 @@ const meta: Meta<typeof ViewBarFilterDropdown> = {
<RecordTableComponentInstanceContext.Provider
value={{
instanceId: instanceId,
onColumnsChange: () => {},
}}
>
<ViewComponentInstanceContext.Provider
@@ -140,7 +140,7 @@ export const RecordTableDecorator: Decorator = (Story, context) => {
return (
<RecordTableComponentInstanceContext.Provider
value={{ instanceId: recordIndexId, onColumnsChange: () => {} }}
value={{ instanceId: recordIndexId }}
>
<ViewComponentInstanceContext.Provider
value={{ instanceId: recordIndexId }}