feat: record group add new (#8925)

Fix #8757

This PR is adding the Add new button on view groups.
Also this PR fix an issue where the pending record can be draggable, and
is causing error.

<img width="1119" alt="Screenshot 2024-12-10 at 4 24 43 PM"
src="https://github.com/user-attachments/assets/4fd01e99-c85e-4a06-a733-cbf3cc32957d">

It also start to issues with the way we're using Context.
We're initializing pretty much all Context like this:

```typescript
export const RecordTableContext = createContext<RecordTableContextProps>(
   {} as RecordTableContextProps,
 );
```

This is causing issues when by mistake we use the context like this
outside the Provider hierarchy:

```typescript
const context = useContext(RecordTableContext);
```

This is going to fail silently, and all the context variables become
undefined...

To fix this I've introduced an util called `createRequiredContext`, this
one is returning an array containing the provider and the hook to
retrieve the context.
The context is initialized to undefined inside this utility, this way we
can check if the value has been initialized with the provider to check
if we're inside it. It'll throw an error if this one is used outside the
provider.
The return values are properly typed, so `undefined` is not added to the
value of the Context.

I'll create a followup ticket to use this new utility function, if
that's ok and replace it everywhere in the codebase.
We can also consider adding a eslint rule to warn about the use of
`createContext` directly.
This commit is contained in:
Jérémy M
2024-12-12 11:50:13 +01:00
committed by GitHub
parent 182ebb6394
commit d7da73f0ec
81 changed files with 1546 additions and 684 deletions

View File

@ -9,7 +9,7 @@ import { OBJECT_FILTER_DROPDOWN_ID } from '@/object-record/object-filter-dropdow
import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown'; import { useFilterDropdown } from '@/object-record/object-filter-dropdown/hooks/useFilterDropdown';
import { useSelectFilter } from '@/object-record/object-filter-dropdown/hooks/useSelectFilter'; import { useSelectFilter } from '@/object-record/object-filter-dropdown/hooks/useSelectFilter';
import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope'; import { FiltersHotkeyScope } from '@/object-record/object-filter-dropdown/types/FiltersHotkeyScope';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { hiddenTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/hiddenTableColumnsComponentSelector'; import { hiddenTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/hiddenTableColumnsComponentSelector';
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector'; import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
@ -20,7 +20,6 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/
import { useGetCurrentView } from '@/views/hooks/useGetCurrentView'; import { useGetCurrentView } from '@/views/hooks/useGetCurrentView';
import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState'; import { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useContext } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-ui'; import { isDefined } from 'twenty-ui';
@ -57,7 +56,7 @@ type ObjectFilterDropdownFilterSelectProps = {
export const ObjectFilterDropdownFilterSelect = ({ export const ObjectFilterDropdownFilterSelect = ({
isAdvancedFilterButtonVisible, isAdvancedFilterButtonVisible,
}: ObjectFilterDropdownFilterSelectProps) => { }: ObjectFilterDropdownFilterSelectProps) => {
const { recordIndexId } = useContext(RecordIndexRootPropsContext); const { recordIndexId } = useRecordIndexContextOrThrow();
const { const {
setObjectFilterDropdownSearchInput, setObjectFilterDropdownSearchInput,

View File

@ -1,9 +1,12 @@
import { Meta, StoryObj } from '@storybook/react'; import { Meta, StoryObj } from '@storybook/react';
import { TaskGroups } from '@/activities/tasks/components/TaskGroups'; import { TaskGroups } from '@/activities/tasks/components/TaskGroups';
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { MultipleFiltersDropdownButton } from '@/object-record/object-filter-dropdown/components/MultipleFiltersDropdownButton'; import { MultipleFiltersDropdownButton } from '@/object-record/object-filter-dropdown/components/MultipleFiltersDropdownButton';
import { ObjectFilterDropdownScope } from '@/object-record/object-filter-dropdown/scopes/ObjectFilterDropdownScope'; import { ObjectFilterDropdownScope } from '@/object-record/object-filter-dropdown/scopes/ObjectFilterDropdownScope';
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext'; 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 { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState'; import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
@ -19,6 +22,7 @@ import { FieldMetadataType } from '~/generated/graphql';
import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator'; import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
const meta: Meta<typeof MultipleFiltersDropdownButton> = { const meta: Meta<typeof MultipleFiltersDropdownButton> = {
title: title:
@ -26,6 +30,9 @@ const meta: Meta<typeof MultipleFiltersDropdownButton> = {
component: MultipleFiltersDropdownButton, component: MultipleFiltersDropdownButton,
decorators: [ decorators: [
(Story) => { (Story) => {
const companyObjectMetadataItem = generatedMockObjectMetadataItems.find(
(item) => item.nameSingular === CoreObjectNameSingular.Company,
)!;
const instanceId = 'entity-tasks-filter-scope'; const instanceId = 'entity-tasks-filter-scope';
const setAvailableFilterDefinitions = useSetRecoilComponentStateV2( const setAvailableFilterDefinitions = useSetRecoilComponentStateV2(
availableFilterDefinitionsComponentState, availableFilterDefinitionsComponentState,
@ -91,19 +98,30 @@ const meta: Meta<typeof MultipleFiltersDropdownButton> = {
}, },
]); ]);
return ( return (
<ObjectFilterDropdownComponentInstanceContext.Provider <RecordIndexContextProvider
value={{ instanceId }} value={{
indexIdentifierUrl: () => '',
onIndexRecordsLoaded: () => {},
objectNamePlural: CoreObjectNamePlural.Company,
objectNameSingular: CoreObjectNameSingular.Company,
objectMetadataItem: companyObjectMetadataItem,
recordIndexId: instanceId,
}}
> >
<RecordTableComponentInstanceContext.Provider <ObjectFilterDropdownComponentInstanceContext.Provider
value={{ instanceId: instanceId, onColumnsChange: () => {} }} value={{ instanceId }}
> >
<ViewComponentInstanceContext.Provider value={{ instanceId }}> <RecordTableComponentInstanceContext.Provider
<ObjectFilterDropdownScope filterScopeId={instanceId}> value={{ instanceId: instanceId, onColumnsChange: () => {} }}
<Story /> >
</ObjectFilterDropdownScope> <ViewComponentInstanceContext.Provider value={{ instanceId }}>
</ViewComponentInstanceContext.Provider> <ObjectFilterDropdownScope filterScopeId={instanceId}>
</RecordTableComponentInstanceContext.Provider> <Story />
</ObjectFilterDropdownComponentInstanceContext.Provider> </ObjectFilterDropdownScope>
</ViewComponentInstanceContext.Provider>
</RecordTableComponentInstanceContext.Provider>
</ObjectFilterDropdownComponentInstanceContext.Provider>
</RecordIndexContextProvider>
); );
}, },
ObjectMetadataItemsDecorator, ObjectMetadataItemsDecorator,

View File

@ -7,7 +7,7 @@ import { ObjectOptionsDropdownContent } from '@/object-record/object-options-dro
import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId'; import { OBJECT_OPTIONS_DROPDOWN_ID } from '@/object-record/object-options-dropdown/constants/ObjectOptionsDropdownId';
import { ObjectOptionsDropdownContext } from '@/object-record/object-options-dropdown/states/contexts/ObjectOptionsDropdownContext'; import { ObjectOptionsDropdownContext } from '@/object-record/object-options-dropdown/states/contexts/ObjectOptionsDropdownContext';
import { ObjectOptionsContentId } from '@/object-record/object-options-dropdown/types/ObjectOptionsContentId'; import { ObjectOptionsContentId } from '@/object-record/object-options-dropdown/types/ObjectOptionsContentId';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
@ -76,11 +76,10 @@ const createStory = (contentId: ObjectOptionsContentId | null): Story => ({
)!; )!;
return ( return (
<RecordIndexRootPropsContext.Provider <RecordIndexContextProvider
value={{ value={{
indexIdentifierUrl: () => '', indexIdentifierUrl: () => '',
onIndexRecordsLoaded: () => {}, onIndexRecordsLoaded: () => {},
onCreateRecord: () => {},
objectNamePlural: 'companies', objectNamePlural: 'companies',
objectNameSingular: 'company', objectNameSingular: 'company',
objectMetadataItem: companyObjectMetadataItem, objectMetadataItem: companyObjectMetadataItem,
@ -102,7 +101,7 @@ const createStory = (contentId: ObjectOptionsContentId | null): Story => ({
<Story /> <Story />
</DropdownMenu> </DropdownMenu>
</ObjectOptionsDropdownContext.Provider> </ObjectOptionsDropdownContext.Provider>
</RecordIndexRootPropsContext.Provider> </RecordIndexContextProvider>
); );
}, },
], ],

View File

@ -1,5 +1,5 @@
import { useSearchRecordGroupField } from '@/object-record/object-options-dropdown/hooks/useSearchRecordGroupField'; import { useSearchRecordGroupField } from '@/object-record/object-options-dropdown/hooks/useSearchRecordGroupField';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
import { renderHook } from '@testing-library/react'; import { renderHook } from '@testing-library/react';
import { act } from 'react'; import { act } from 'react';
@ -11,13 +11,13 @@ describe('useSearchRecordGroupField', () => {
renderHook(() => useSearchRecordGroupField(), { renderHook(() => useSearchRecordGroupField(), {
wrapper: ({ children }) => ( wrapper: ({ children }) => (
<RecoilRoot> <RecoilRoot>
<RecordIndexRootPropsContext.Provider value={contextValue}> <RecordIndexContextProvider value={contextValue}>
<ViewComponentInstanceContext.Provider <ViewComponentInstanceContext.Provider
value={{ instanceId: 'myViewInstanceId' }} value={{ instanceId: 'myViewInstanceId' }}
> >
{children} {children}
</ViewComponentInstanceContext.Provider> </ViewComponentInstanceContext.Provider>
</RecordIndexRootPropsContext.Provider> </RecordIndexContextProvider>
</RecoilRoot> </RecoilRoot>
), ),
}); });

View File

@ -1,11 +1,11 @@
import { objectOptionsDropdownSearchInputComponentState } from '@/object-record/object-options-dropdown/states/objectOptionsDropdownSearchInputComponentState'; import { objectOptionsDropdownSearchInputComponentState } from '@/object-record/object-options-dropdown/states/objectOptionsDropdownSearchInputComponentState';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2'; import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { useContext, useMemo } from 'react'; import { useMemo } from 'react';
import { FieldMetadataType } from '~/generated-metadata/graphql'; import { FieldMetadataType } from '~/generated-metadata/graphql';
export const useSearchRecordGroupField = () => { export const useSearchRecordGroupField = () => {
const { objectMetadataItem } = useContext(RecordIndexRootPropsContext); const { objectMetadataItem } = useRecordIndexContextOrThrow();
const [recordGroupFieldSearchInput, setRecordGroupFieldSearchInput] = const [recordGroupFieldSearchInput, setRecordGroupFieldSearchInput] =
useRecoilComponentStateV2(objectOptionsDropdownSearchInputComponentState); useRecoilComponentStateV2(objectOptionsDropdownSearchInputComponentState);

View File

@ -5,7 +5,7 @@ import { IconChevronDown, MenuItem, useIcons } from 'twenty-ui';
import { OBJECT_SORT_DROPDOWN_ID } from '@/object-record/object-sort-dropdown/constants/ObjectSortDropdownId'; import { OBJECT_SORT_DROPDOWN_ID } from '@/object-record/object-sort-dropdown/constants/ObjectSortDropdownId';
import { useObjectSortDropdown } from '@/object-record/object-sort-dropdown/hooks/useObjectSortDropdown'; import { useObjectSortDropdown } from '@/object-record/object-sort-dropdown/hooks/useObjectSortDropdown';
import { ObjectSortDropdownScope } from '@/object-record/object-sort-dropdown/scopes/ObjectSortDropdownScope'; import { ObjectSortDropdownScope } from '@/object-record/object-sort-dropdown/scopes/ObjectSortDropdownScope';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { hiddenTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/hiddenTableColumnsComponentSelector'; import { hiddenTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/hiddenTableColumnsComponentSelector';
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector'; import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
@ -16,7 +16,6 @@ import { StyledHeaderDropdownButton } from '@/ui/layout/dropdown/components/Styl
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useContext } from 'react';
import { SORT_DIRECTIONS } from '../types/SortDirection'; import { SORT_DIRECTIONS } from '../types/SortDirection';
export const StyledInput = styled.input` export const StyledInput = styled.input`
@ -77,7 +76,7 @@ export const ObjectSortDropdownButton = ({
resetSearchInput, resetSearchInput,
} = useObjectSortDropdown(); } = useObjectSortDropdown();
const { recordIndexId } = useContext(RecordIndexRootPropsContext); const { recordIndexId } = useRecordIndexContextOrThrow();
const { isDropdownOpen } = useDropdown(OBJECT_SORT_DROPDOWN_ID); const { isDropdownOpen } = useDropdown(OBJECT_SORT_DROPDOWN_ID);

View File

@ -1,15 +1,41 @@
import { Meta, StoryObj } from '@storybook/react'; import { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from 'twenty-ui'; import { ComponentDecorator } from 'twenty-ui';
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { ChipFieldDisplay } from '@/object-record/record-field/meta-types/display/components/ChipFieldDisplay'; import { ChipFieldDisplay } from '@/object-record/record-field/meta-types/display/components/ChipFieldDisplay';
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
import { ChipGeneratorsDecorator } from '~/testing/decorators/ChipGeneratorsDecorator'; import { ChipGeneratorsDecorator } from '~/testing/decorators/ChipGeneratorsDecorator';
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator'; import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator'; import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory'; import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
const meta: Meta = { const meta: Meta = {
title: 'UI/Data/Field/Display/ChipFieldDisplay', title: 'UI/Data/Field/Display/ChipFieldDisplay',
decorators: [ decorators: [
(Story) => {
const instanceId = 'child-field-display-scope';
const companyObjectMetadataItem = generatedMockObjectMetadataItems.find(
(item) => item.nameSingular === CoreObjectNameSingular.Company,
)!;
return (
<RecordIndexContextProvider
value={{
indexIdentifierUrl: () => '',
onIndexRecordsLoaded: () => {},
objectNamePlural: CoreObjectNamePlural.Company,
objectNameSingular: CoreObjectNameSingular.Company,
objectMetadataItem: companyObjectMetadataItem,
recordIndexId: instanceId,
}}
>
<Story />
</RecordIndexContextProvider>
);
},
MemoryRouterDecorator, MemoryRouterDecorator,
ChipGeneratorsDecorator, ChipGeneratorsDecorator,
getFieldDecorator('person', 'name'), getFieldDecorator('person', 'name'),

View File

@ -1,7 +1,7 @@
import { RecordGroupContext } from '@/object-record/record-group/states/context/RecordGroupContext'; import { RecordGroupContext } from '@/object-record/record-group/states/context/RecordGroupContext';
import { useContext } from 'react'; import { useContext } from 'react';
export const useCurrentRecordGroupId = () => { export const useCurrentRecordGroupId = (): string => {
const context = useContext(RecordGroupContext); const context = useContext(RecordGroupContext);
if (!context) { if (!context) {

View File

@ -5,7 +5,7 @@ import { RecordBoardColumnContext } from '@/object-record/record-board/record-bo
import { useRecordGroupVisibility } from '@/object-record/record-group/hooks/useRecordGroupVisibility'; import { useRecordGroupVisibility } from '@/object-record/record-group/hooks/useRecordGroupVisibility';
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState'; import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
import { RecordGroupAction } from '@/object-record/record-group/types/RecordGroupActions'; import { RecordGroupAction } from '@/object-record/record-group/types/RecordGroupActions';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState'; import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useCallback, useContext, useMemo } from 'react'; import { useCallback, useContext, useMemo } from 'react';
@ -17,9 +17,7 @@ export const useRecordGroupActions = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
const { objectNameSingular, recordIndexId } = useContext( const { objectNameSingular, recordIndexId } = useRecordIndexContextOrThrow();
RecordIndexRootPropsContext,
);
const { columnDefinition: recordGroupDefinition } = useContext( const { columnDefinition: recordGroupDefinition } = useContext(
RecordBoardColumnContext, RecordBoardColumnContext,

View File

@ -2,15 +2,14 @@ import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/s
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState'; import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
import { recordGroupIdsComponentState } from '@/object-record/record-group/states/recordGroupIdsComponentState'; import { recordGroupIdsComponentState } from '@/object-record/record-group/states/recordGroupIdsComponentState';
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition'; import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { useContext } from 'react';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
export const useSetRecordGroup = (viewId?: string) => { export const useSetRecordGroup = (viewId?: string) => {
const { objectMetadataItem } = useContext(RecordIndexRootPropsContext); const { objectMetadataItem } = useRecordIndexContextOrThrow();
const recordIndexRecordGroupIdsState = useRecoilComponentCallbackStateV2( const recordIndexRecordGroupIdsState = useRecoilComponentCallbackStateV2(
recordGroupIdsComponentState, recordGroupIdsComponentState,

View File

@ -17,7 +17,7 @@ import { recordIndexSortsState } from '@/object-record/record-index/states/recor
import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState'; import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState';
import { InformationBannerWrapper } from '@/information-banner/components/InformationBannerWrapper'; import { InformationBannerWrapper } from '@/information-banner/components/InformationBannerWrapper';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext'; import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { SpreadsheetImportProvider } from '@/spreadsheet-import/provider/components/SpreadsheetImportProvider'; import { SpreadsheetImportProvider } from '@/spreadsheet-import/provider/components/SpreadsheetImportProvider';
@ -39,7 +39,7 @@ import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
import { mapViewGroupsToRecordGroupDefinitions } from '@/views/utils/mapViewGroupsToRecordGroupDefinitions'; import { mapViewGroupsToRecordGroupDefinitions } from '@/views/utils/mapViewGroupsToRecordGroupDefinitions';
import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts'; import { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useCallback, useContext } from 'react'; import { useCallback } from 'react';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
const StyledContainer = styled.div` const StyledContainer = styled.div`
@ -67,7 +67,7 @@ export const RecordIndexContainer = () => {
recordIndexId, recordIndexId,
objectMetadataItem, objectMetadataItem,
objectNameSingular, objectNameSingular,
} = useContext(RecordIndexRootPropsContext); } = useRecordIndexContextOrThrow();
const setRecordGroup = useSetRecordGroup(recordIndexId); const setRecordGroup = useSetRecordGroup(recordIndexId);

View File

@ -5,11 +5,11 @@ import { computeContextStoreFilters } from '@/context-store/utils/computeContext
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural'; import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useFindManyRecordIndexTableParams } from '@/object-record/record-index/hooks/useFindManyRecordIndexTableParams'; import { useFindManyRecordIndexTableParams } from '@/object-record/record-index/hooks/useFindManyRecordIndexTableParams';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { useContext, useEffect } from 'react'; import { useEffect } from 'react';
export const RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect = export const RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect =
() => { () => {
@ -21,7 +21,7 @@ export const RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect =
contextStoreTargetedRecordsRuleComponentState, contextStoreTargetedRecordsRuleComponentState,
); );
const { objectNamePlural } = useContext(RecordIndexRootPropsContext); const { objectNamePlural } = useRecordIndexContextOrThrow();
const { objectNameSingular } = useObjectNameSingularFromPlural({ const { objectNameSingular } = useObjectNameSingularFromPlural({
objectNamePlural, objectNamePlural,

View File

@ -1,15 +1,15 @@
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState'; import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural'; import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { useContext, useEffect } from 'react'; import { useEffect } from 'react';
export const RecordIndexContainerContextStoreObjectMetadataEffect = () => { export const RecordIndexContainerContextStoreObjectMetadataEffect = () => {
const setContextStoreCurrentObjectMetadataItem = useSetRecoilComponentStateV2( const setContextStoreCurrentObjectMetadataItem = useSetRecoilComponentStateV2(
contextStoreCurrentObjectMetadataIdComponentState, contextStoreCurrentObjectMetadataIdComponentState,
); );
const { objectNamePlural } = useContext(RecordIndexRootPropsContext); const { objectNamePlural } = useRecordIndexContextOrThrow();
const { objectNameSingular } = useObjectNameSingularFromPlural({ const { objectNameSingular } = useObjectNameSingularFromPlural({
objectNamePlural, objectNamePlural,

View File

@ -1,8 +1,8 @@
import { useContext, useEffect } from 'react'; import { useEffect } from 'react';
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState'; import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState'; import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState'; import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState';
import { hasUserSelectedAllRowsComponentState } from '@/object-record/record-table/record-table-row/states/hasUserSelectedAllRowsFamilyState'; import { hasUserSelectedAllRowsComponentState } from '@/object-record/record-table/record-table-row/states/hasUserSelectedAllRowsFamilyState';
import { selectedRowIdsComponentSelector } from '@/object-record/record-table/states/selectors/selectedRowIdsComponentSelector'; import { selectedRowIdsComponentSelector } from '@/object-record/record-table/states/selectors/selectedRowIdsComponentSelector';
@ -12,7 +12,7 @@ import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-sta
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
export const RecordIndexFiltersToContextStoreEffect = () => { export const RecordIndexFiltersToContextStoreEffect = () => {
const { recordIndexId } = useContext(RecordIndexRootPropsContext); const { recordIndexId } = useRecordIndexContextOrThrow();
const recordIndexFilters = useRecoilValue(recordIndexFiltersState); const recordIndexFilters = useRecoilValue(recordIndexFiltersState);

View File

@ -3,16 +3,14 @@ import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-sto
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems'; import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { isObjectMetadataReadOnly } from '@/object-metadata/utils/isObjectMetadataReadOnly'; import { isObjectMetadataReadOnly } from '@/object-metadata/utils/isObjectMetadataReadOnly';
import { RecordIndexPageKanbanAddButton } from '@/object-record/record-index/components/RecordIndexPageKanbanAddButton'; import { RecordIndexPageKanbanAddButton } from '@/object-record/record-index/components/RecordIndexPageKanbanAddButton';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { RecordIndexPageTableAddButton } from '@/object-record/record-index/components/RecordIndexPageTableAddButton';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState'; import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState';
import { PageHeaderOpenCommandMenuButton } from '@/ui/layout/page-header/components/PageHeaderOpenCommandMenuButton'; import { PageHeaderOpenCommandMenuButton } from '@/ui/layout/page-header/components/PageHeaderOpenCommandMenuButton';
import { PageAddButton } from '@/ui/layout/page/components/PageAddButton';
import { PageHeader } from '@/ui/layout/page/components/PageHeader'; import { PageHeader } from '@/ui/layout/page/components/PageHeader';
import { PageHotkeysEffect } from '@/ui/layout/page/components/PageHotkeysEffect';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { ViewType } from '@/views/types/ViewType'; import { ViewType } from '@/views/types/ViewType';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useContext } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { isDefined, useIcons } from 'twenty-ui'; import { isDefined, useIcons } from 'twenty-ui';
import { capitalize } from '~/utils/string/capitalize'; import { capitalize } from '~/utils/string/capitalize';
@ -21,17 +19,13 @@ export const RecordIndexPageHeader = () => {
const { findObjectMetadataItemByNamePlural } = const { findObjectMetadataItemByNamePlural } =
useFilteredObjectMetadataItems(); useFilteredObjectMetadataItems();
const { objectNamePlural, onCreateRecord } = useContext( const { objectNamePlural } = useRecordIndexContextOrThrow();
RecordIndexRootPropsContext,
);
const objectMetadataItem = const objectMetadataItem =
findObjectMetadataItemByNamePlural(objectNamePlural); findObjectMetadataItemByNamePlural(objectNamePlural);
const { getIcon } = useIcons(); const { getIcon } = useIcons();
const Icon = getIcon( const Icon = getIcon(objectMetadataItem?.icon);
findObjectMetadataItemByNamePlural(objectNamePlural)?.icon,
);
const recordIndexViewType = useRecoilValue(recordIndexViewTypeState); const recordIndexViewType = useRecoilValue(recordIndexViewTypeState);
@ -56,17 +50,14 @@ export const RecordIndexPageHeader = () => {
const pageHeaderTitle = const pageHeaderTitle =
objectMetadataItem?.labelPlural ?? capitalize(objectNamePlural); objectMetadataItem?.labelPlural ?? capitalize(objectNamePlural);
const handleAddButtonClick = () => {
onCreateRecord();
};
return ( return (
<PageHeader title={pageHeaderTitle} Icon={Icon}> <PageHeader title={pageHeaderTitle} Icon={Icon}>
<PageHotkeysEffect onAddButtonClick={handleAddButtonClick} />
{shouldDisplayAddButton && {shouldDisplayAddButton &&
/**
* TODO: Logic between Table and Kanban should be merged here when we move some states to record-index
*/
(isTable ? ( (isTable ? (
<PageAddButton onClick={handleAddButtonClick} /> <RecordIndexPageTableAddButton />
) : ( ) : (
<RecordIndexPageKanbanAddButton /> <RecordIndexPageKanbanAddButton />
))} ))}

View File

@ -1,4 +1,3 @@
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard'; import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard';
import { useIsOpportunitiesCompanyFieldDisabled } from '@/object-record/record-board/record-board-column/hooks/useIsOpportunitiesCompanyFieldDisabled'; import { useIsOpportunitiesCompanyFieldDisabled } from '@/object-record/record-board/record-board-column/hooks/useIsOpportunitiesCompanyFieldDisabled';
@ -6,33 +5,20 @@ import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-re
import { visibleRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentSelector'; import { visibleRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentSelector';
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition'; import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
import { RecordIndexPageKanbanAddMenuItem } from '@/object-record/record-index/components/RecordIndexPageKanbanAddMenuItem'; import { RecordIndexPageKanbanAddMenuItem } from '@/object-record/record-index/components/RecordIndexPageKanbanAddMenuItem';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState'; import { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { PageAddButton } from '@/ui/layout/page/components/PageAddButton'; import { PageAddButton } from '@/ui/layout/page/components/PageAddButton';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import styled from '@emotion/styled'; import { useCallback } from 'react';
import { useCallback, useContext } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
const StyledDropdownMenuItemsContainer = styled(DropdownMenuItemsContainer)`
width: 100%;
`;
const StyledDropDownMenu = styled(DropdownMenu)`
width: 200px;
`;
export const RecordIndexPageKanbanAddButton = () => { export const RecordIndexPageKanbanAddButton = () => {
const dropdownId = `record-index-page-add-button-dropdown`; const dropdownId = `record-index-page-add-button-dropdown`;
const { recordIndexId, objectNameSingular } = useContext( const { recordIndexId, objectMetadataItem } = useRecordIndexContextOrThrow();
RecordIndexRootPropsContext,
);
const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular });
const visibleRecordGroupIds = useRecoilComponentValueV2( const visibleRecordGroupIds = useRecoilComponentValueV2(
visibleRecordGroupIdsComponentSelector, visibleRecordGroupIdsComponentSelector,
@ -95,17 +81,15 @@ export const RecordIndexPageKanbanAddButton = () => {
clickableComponent={<PageAddButton />} clickableComponent={<PageAddButton />}
dropdownId={dropdownId} dropdownId={dropdownId}
dropdownComponents={ dropdownComponents={
<StyledDropDownMenu> <DropdownMenuItemsContainer>
<StyledDropdownMenuItemsContainer> {visibleRecordGroupIds.map((recordGroupId) => (
{visibleRecordGroupIds.map((recordGroupId) => ( <RecordIndexPageKanbanAddMenuItem
<RecordIndexPageKanbanAddMenuItem key={recordGroupId}
key={recordGroupId} columnId={recordGroupId}
columnId={recordGroupId} onItemClick={handleItemClick}
onItemClick={handleItemClick} />
/> ))}
))} </DropdownMenuItemsContainer>
</StyledDropdownMenuItemsContainer>
</StyledDropDownMenu>
} }
dropdownHotkeyScope={{ scope: dropdownId }} dropdownHotkeyScope={{ scope: dropdownId }}
/> />

View File

@ -0,0 +1,16 @@
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
import { RecordIndexPageTableAddButtonInGroup } from '@/object-record/record-index/components/RecordIndexPageTableAddButtonInGroup';
import { RecordIndexPageTableAddButtonNoGroup } from '@/object-record/record-index/components/RecordIndexPageTableAddButtonNoGroup';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
export const RecordIndexPageTableAddButton = () => {
const hasRecordGroups = useRecoilComponentValueV2(
hasRecordGroupsComponentSelector,
);
if (!hasRecordGroups) {
return <RecordIndexPageTableAddButtonNoGroup />;
}
return <RecordIndexPageTableAddButtonInGroup />;
};

View File

@ -0,0 +1,80 @@
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
import { visibleRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentSelector';
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
import { RecordIndexPageKanbanAddMenuItem } from '@/object-record/record-index/components/RecordIndexPageKanbanAddMenuItem';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useCreateNewTableRecordInGroup } from '@/object-record/record-table/hooks/useCreateNewTableRecordInGroup';
import { isRecordGroupTableSectionToggledComponentState } from '@/object-record/record-table/record-table-section/states/isRecordGroupTableSectionToggledComponentState';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
import { PageAddButton } from '@/ui/layout/page/components/PageAddButton';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useRecoilCallback } from 'recoil';
export const RecordIndexPageTableAddButtonInGroup = () => {
const dropdownId = `record-index-page-table-add-button-dropdown`;
const { objectMetadataItem } = useRecordIndexContextOrThrow();
const visibleRecordGroupIds = useRecoilComponentValueV2(
visibleRecordGroupIdsComponentSelector,
);
const recordGroupFieldMetadata = useRecoilComponentValueV2(
recordGroupFieldMetadataComponentState,
);
const isRecordGroupTableSectionToggledState =
useRecoilComponentCallbackStateV2(
isRecordGroupTableSectionToggledComponentState,
);
const selectFieldMetadataItem = objectMetadataItem.fields.find(
(field) => field.id === recordGroupFieldMetadata?.id,
);
const { createNewTableRecordInGroup } = useCreateNewTableRecordInGroup();
const { closeDropdown } = useDropdown(dropdownId);
const handleCreateNewTableRecordInGroup = useRecoilCallback(
({ set }) =>
(recordGroup: RecordGroupDefinition) => {
set(isRecordGroupTableSectionToggledState(recordGroup.id), true);
createNewTableRecordInGroup(recordGroup.id);
closeDropdown();
},
[
closeDropdown,
createNewTableRecordInGroup,
isRecordGroupTableSectionToggledState,
],
);
if (!selectFieldMetadataItem) {
return null;
}
return (
<Dropdown
dropdownMenuWidth="200px"
dropdownPlacement="bottom-start"
clickableComponent={<PageAddButton />}
dropdownId={dropdownId}
dropdownComponents={
<DropdownMenuItemsContainer>
{visibleRecordGroupIds.map((recordGroupId) => (
<RecordIndexPageKanbanAddMenuItem
key={recordGroupId}
columnId={recordGroupId}
onItemClick={handleCreateNewTableRecordInGroup}
/>
))}
</DropdownMenuItemsContainer>
}
dropdownHotkeyScope={{ scope: dropdownId }}
/>
);
};

View File

@ -0,0 +1,21 @@
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords';
import { PageAddButton } from '@/ui/layout/page/components/PageAddButton';
import { PageHotkeysEffect } from '@/ui/layout/page/components/PageHotkeysEffect';
export const RecordIndexPageTableAddButtonNoGroup = () => {
const { recordIndexId } = useRecordIndexContextOrThrow();
const { createNewTableRecord } = useCreateNewTableRecord(recordIndexId);
const handleCreateNewTableRecord = () => {
createNewTableRecord();
};
return (
<>
<PageHotkeysEffect onAddButtonClick={handleCreateNewTableRecord} />
<PageAddButton onClick={handleCreateNewTableRecord} />
</>
);
};

View File

@ -1,8 +1,7 @@
import { useGetStandardObjectIcon } from '@/object-metadata/hooks/useGetStandardObjectIcon'; import { useGetStandardObjectIcon } from '@/object-metadata/hooks/useGetStandardObjectIcon';
import { useRecordChipData } from '@/object-record/hooks/useRecordChipData'; import { useRecordChipData } from '@/object-record/hooks/useRecordChipData';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { useContext } from 'react';
import { AvatarChip, AvatarChipVariant, ChipSize } from 'twenty-ui'; import { AvatarChip, AvatarChipVariant, ChipSize } from 'twenty-ui';
export type RecordIdentifierChipProps = { export type RecordIdentifierChipProps = {
@ -20,7 +19,7 @@ export const RecordIdentifierChip = ({
size, size,
maxWidth, maxWidth,
}: RecordIdentifierChipProps) => { }: RecordIdentifierChipProps) => {
const { indexIdentifierUrl } = useContext(RecordIndexRootPropsContext); const { indexIdentifierUrl } = useRecordIndexContextOrThrow();
const { recordChipData } = useRecordChipData({ const { recordChipData } = useRecordChipData({
objectNameSingular, objectNameSingular,
record, record,

View File

@ -1,9 +1,8 @@
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { RecordUpdateHookParams } from '@/object-record/record-field/contexts/FieldContext'; import { RecordUpdateHookParams } from '@/object-record/record-field/contexts/FieldContext';
import { RecordIndexRemoveSortingModal } from '@/object-record/record-index/components/RecordIndexRemoveSortingModal'; import { RecordIndexRemoveSortingModal } from '@/object-record/record-index/components/RecordIndexRemoveSortingModal';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { RecordTableWithWrappers } from '@/object-record/record-table/components/RecordTableWithWrappers'; import { RecordTableWithWrappers } from '@/object-record/record-table/components/RecordTableWithWrappers';
import { useContext } from 'react';
type RecordIndexTableContainerProps = { type RecordIndexTableContainerProps = {
recordTableId: string; recordTableId: string;
@ -14,7 +13,7 @@ export const RecordIndexTableContainer = ({
recordTableId, recordTableId,
viewBarId, viewBarId,
}: RecordIndexTableContainerProps) => { }: RecordIndexTableContainerProps) => {
const { objectNameSingular } = useContext(RecordIndexRootPropsContext); const { objectNameSingular } = useRecordIndexContextOrThrow();
const { updateOneRecord } = useUpdateOneRecord({ const { updateOneRecord } = useUpdateOneRecord({
objectNameSingular, objectNameSingular,

View File

@ -1,8 +1,8 @@
import { useContext, useEffect } from 'react'; import { useEffect } from 'react';
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata'; import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useHandleToggleColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleColumnFilter'; import { useHandleToggleColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleColumnFilter';
import { useHandleToggleColumnSort } from '@/object-record/record-index/hooks/useHandleToggleColumnSort'; import { useHandleToggleColumnSort } from '@/object-record/record-index/hooks/useHandleToggleColumnSort';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
@ -13,9 +13,7 @@ import { ViewField } from '@/views/types/ViewField';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
export const RecordIndexTableContainerEffect = () => { export const RecordIndexTableContainerEffect = () => {
const { recordIndexId, objectNameSingular } = useContext( const { recordIndexId, objectNameSingular } = useRecordIndexContextOrThrow();
RecordIndexRootPropsContext,
);
const viewBarId = recordIndexId; const viewBarId = recordIndexId;

View File

@ -1,15 +1,14 @@
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { createRootPropsContext } from '~/utils/createRootPropsContext'; import { createRequiredContext } from '~/utils/createRequiredContext';
export type RecordIndexRootPropsContextProps = { export type RecordIndexContextValue = {
indexIdentifierUrl: (recordId: string) => string; indexIdentifierUrl: (recordId: string) => string;
onIndexRecordsLoaded: () => void; onIndexRecordsLoaded: () => void;
onCreateRecord: () => void;
objectNamePlural: string; objectNamePlural: string;
objectNameSingular: string; objectNameSingular: string;
objectMetadataItem: ObjectMetadataItem; objectMetadataItem: ObjectMetadataItem;
recordIndexId: string; recordIndexId: string;
}; };
export const RecordIndexRootPropsContext = export const [RecordIndexContextProvider, useRecordIndexContextOrThrow] =
createRootPropsContext<RecordIndexRootPropsContextProps>(); createRequiredContext<RecordIndexContextValue>('RecordIndexContext');

View File

@ -3,10 +3,9 @@ import { isNonEmptyString, isNull } from '@sniptt/guards';
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector'; import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector'; import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider';
import { RecordTableStickyEffect } from '@/object-record/record-table/components/RecordTableStickyEffect'; import { RecordTableStickyEffect } from '@/object-record/record-table/components/RecordTableStickyEffect';
import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId'; import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableEmptyState } from '@/object-record/record-table/empty-state/components/RecordTableEmptyState'; import { RecordTableEmptyState } from '@/object-record/record-table/empty-state/components/RecordTableEmptyState';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { RecordTableBodyUnselectEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyUnselectEffect'; import { RecordTableBodyUnselectEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyUnselectEffect';
@ -29,19 +28,9 @@ const StyledTable = styled.table`
width: 100%; width: 100%;
`; `;
type RecordTableProps = { export const RecordTable = () => {
viewBarId: string; const { recordTableId, objectNameSingular } = useRecordTableContextOrThrow();
recordTableId: string;
objectNameSingular: string;
onColumnsChange: (columns: any) => void;
};
export const RecordTable = ({
viewBarId,
recordTableId,
objectNameSingular,
onColumnsChange,
}: RecordTableProps) => {
const tableBodyRef = useRef<HTMLTableElement>(null); const tableBodyRef = useRef<HTMLTableElement>(null);
const { toggleClickOutsideListener } = useClickOutsideListener( const { toggleClickOutsideListener } = useClickOutsideListener(
@ -82,51 +71,39 @@ export const RecordTable = ({
} }
return ( return (
<RecordTableComponentInstance <>
recordTableId={recordTableId} {!hasRecordGroups ? (
onColumnsChange={onColumnsChange} <RecordTableNoRecordGroupBodyEffect />
> ) : (
<RecordTableContextProvider <RecordTableRecordGroupBodyEffects />
objectNameSingular={objectNameSingular} )}
recordTableId={recordTableId} <RecordTableBodyUnselectEffect tableBodyRef={tableBodyRef} />
viewBarId={viewBarId} {recordTableIsEmpty ? (
> <RecordTableEmptyState />
{!hasRecordGroups ? ( ) : (
<RecordTableNoRecordGroupBodyEffect /> <>
) : ( <StyledTable className="entity-table-cell" ref={tableBodyRef}>
<RecordTableRecordGroupBodyEffects /> <RecordTableHeader />
)} {!hasRecordGroups ? (
<RecordTableBodyUnselectEffect <RecordTableNoRecordGroupBody />
tableBodyRef={tableBodyRef} ) : (
recordTableId={recordTableId} <RecordTableRecordGroupsBody />
/> )}
{recordTableIsEmpty ? ( <RecordTableStickyEffect />
<RecordTableEmptyState /> </StyledTable>
) : ( <DragSelect
<> dragSelectable={tableBodyRef}
<StyledTable className="entity-table-cell" ref={tableBodyRef}> onDragSelectionStart={() => {
<RecordTableHeader objectNameSingular={objectNameSingular} /> resetTableRowSelection();
{!hasRecordGroups ? ( toggleClickOutsideListener(false);
<RecordTableNoRecordGroupBody /> }}
) : ( onDragSelectionChange={setRowSelected}
<RecordTableRecordGroupsBody /> onDragSelectionEnd={() => {
)} toggleClickOutsideListener(true);
<RecordTableStickyEffect /> }}
</StyledTable> />
<DragSelect </>
dragSelectable={tableBodyRef} )}
onDragSelectionStart={() => { </>
resetTableRowSelection();
toggleClickOutsideListener(false);
}}
onDragSelectionChange={setRowSelected}
onDragSelectionEnd={() => {
toggleClickOutsideListener(true);
}}
/>
</>
)}
</RecordTableContextProvider>
</RecordTableComponentInstance>
); );
}; };

View File

@ -1,117 +1,44 @@
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; import { RecordTableContextProvider as RecordTableContextInternalProvider } from '@/object-record/record-table/contexts/RecordTableContext';
import { useHandleContainerMouseEnter } from '@/object-record/record-table/hooks/internal/useHandleContainerMouseEnter';
import { useRecordTableMoveFocus } from '@/object-record/record-table/hooks/useRecordTableMoveFocus';
import { useCloseRecordTableCellV2 } from '@/object-record/record-table/record-table-cell/hooks/useCloseRecordTableCellV2';
import { useMoveSoftFocusToCellOnHoverV2 } from '@/object-record/record-table/record-table-cell/hooks/useMoveSoftFocusToCellOnHoverV2';
import {
OpenTableCellArgs,
useOpenRecordTableCellV2,
} from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
import { useTriggerActionMenuDropdown } from '@/object-record/record-table/record-table-cell/hooks/useTriggerActionMenuDropdown';
import { useUpsertRecord } from '@/object-record/record-table/record-table-cell/hooks/useUpsertRecord';
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector'; import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
import { MoveFocusDirection } from '@/object-record/record-table/types/MoveFocusDirection';
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
type RecordTableContextProviderProps = {
viewBarId: string;
recordTableId: string;
objectNameSingular: string;
children: ReactNode;
};
export const RecordTableContextProvider = ({ export const RecordTableContextProvider = ({
viewBarId, viewBarId,
recordTableId, recordTableId,
objectNameSingular, objectNameSingular,
children, children,
}: { }: RecordTableContextProviderProps) => {
viewBarId: string;
recordTableId: string;
objectNameSingular: string;
children: ReactNode;
}) => {
const { objectMetadataItem } = useObjectMetadataItem({ const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular, objectNameSingular,
}); });
const { upsertRecord } = useUpsertRecord({
objectNameSingular,
recordTableId,
});
const handleUpsertRecord = ({
persistField,
recordId,
fieldName,
}: {
persistField: () => void;
recordId: string;
fieldName: string;
}) => {
upsertRecord(persistField, recordId, fieldName);
};
const { openTableCell } = useOpenRecordTableCellV2(recordTableId);
const handleOpenTableCell = (args: OpenTableCellArgs) => {
openTableCell(args);
};
const { moveFocus } = useRecordTableMoveFocus(recordTableId);
const handleMoveFocus = (direction: MoveFocusDirection) => {
moveFocus(direction);
};
const { closeTableCell } = useCloseRecordTableCellV2(recordTableId);
const handleCloseTableCell = () => {
closeTableCell();
};
const { moveSoftFocusToCell } =
useMoveSoftFocusToCellOnHoverV2(recordTableId);
const handleMoveSoftFocusToCell = (cellPosition: TableCellPosition) => {
moveSoftFocusToCell(cellPosition);
};
const { triggerActionMenuDropdown } = useTriggerActionMenuDropdown({
recordTableId,
});
const handleActionMenuDropdown = (
event: React.MouseEvent,
recordId: string,
) => {
triggerActionMenuDropdown(event, recordId);
};
const { handleContainerMouseEnter } = useHandleContainerMouseEnter({
recordTableId,
});
const visibleTableColumns = useRecoilComponentValueV2( const visibleTableColumns = useRecoilComponentValueV2(
visibleTableColumnsComponentSelector, visibleTableColumnsComponentSelector,
recordTableId, recordTableId,
); );
return ( return (
<RecordTableContext.Provider <RecordTableContextInternalProvider
value={{ value={{
viewBarId, viewBarId,
objectMetadataItem, objectMetadataItem,
onUpsertRecord: handleUpsertRecord,
onOpenTableCell: handleOpenTableCell,
onMoveFocus: handleMoveFocus,
onCloseTableCell: handleCloseTableCell,
onMoveSoftFocusToCell: handleMoveSoftFocusToCell,
onActionMenuDropdownOpened: handleActionMenuDropdown,
onCellMouseEnter: handleContainerMouseEnter,
visibleTableColumns, visibleTableColumns,
recordTableId, recordTableId,
objectNameSingular, objectNameSingular,
}} }}
> >
{children} {children}
</RecordTableContext.Provider> </RecordTableContextInternalProvider>
); );
}; };

View File

@ -0,0 +1,95 @@
import { RecordTableBodyContextProvider } from '@/object-record/record-table/contexts/RecordTableBodyContext';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useHandleContainerMouseEnter } from '@/object-record/record-table/hooks/internal/useHandleContainerMouseEnter';
import { useUpsertTableRecordNoGroup } from '@/object-record/record-table/hooks/internal/useUpsertTableRecordNoGroup';
import { useRecordTableMoveFocus } from '@/object-record/record-table/hooks/useRecordTableMoveFocus';
import { useCloseRecordTableCellNoGroup } from '@/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellNoGroup';
import { useMoveSoftFocusToCellOnHoverV2 } from '@/object-record/record-table/record-table-cell/hooks/useMoveSoftFocusToCellOnHoverV2';
import {
OpenTableCellArgs,
useOpenRecordTableCellV2,
} from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
import { useTriggerActionMenuDropdown } from '@/object-record/record-table/record-table-cell/hooks/useTriggerActionMenuDropdown';
import { MoveFocusDirection } from '@/object-record/record-table/types/MoveFocusDirection';
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
import { ReactNode } from 'react';
type RecordTableNoRecordGroupBodyContextProviderProps = {
children?: ReactNode;
};
export const RecordTableNoRecordGroupBodyContextProvider = ({
children,
}: RecordTableNoRecordGroupBodyContextProviderProps) => {
const { recordTableId } = useRecordTableContextOrThrow();
const { upsertTableRecordNoGroup } = useUpsertTableRecordNoGroup();
const handleUpsertTableRecordNoRecordGroup = ({
persistField,
recordId,
fieldName,
}: {
persistField: () => void;
recordId: string;
fieldName: string;
}) => {
upsertTableRecordNoGroup(persistField, recordId, fieldName);
};
const { openTableCell } = useOpenRecordTableCellV2(recordTableId);
const handleOpenTableCell = (args: OpenTableCellArgs) => {
openTableCell(args);
};
const { moveFocus } = useRecordTableMoveFocus(recordTableId);
const handleMoveFocus = (direction: MoveFocusDirection) => {
moveFocus(direction);
};
const { closeTableCellNoGroup } = useCloseRecordTableCellNoGroup();
const handleCloseTableCell = () => {
closeTableCellNoGroup();
};
const { moveSoftFocusToCell } =
useMoveSoftFocusToCellOnHoverV2(recordTableId);
const handleMoveSoftFocusToCell = (cellPosition: TableCellPosition) => {
moveSoftFocusToCell(cellPosition);
};
const { triggerActionMenuDropdown } = useTriggerActionMenuDropdown({
recordTableId,
});
const handleActionMenuDropdown = (
event: React.MouseEvent,
recordId: string,
) => {
triggerActionMenuDropdown(event, recordId);
};
const { handleContainerMouseEnter } = useHandleContainerMouseEnter({
recordTableId,
});
return (
<RecordTableBodyContextProvider
value={{
onUpsertRecord: handleUpsertTableRecordNoRecordGroup,
onOpenTableCell: handleOpenTableCell,
onMoveFocus: handleMoveFocus,
onCloseTableCell: handleCloseTableCell,
onMoveSoftFocusToCell: handleMoveSoftFocusToCell,
onActionMenuDropdownOpened: handleActionMenuDropdown,
onCellMouseEnter: handleContainerMouseEnter,
}}
>
{children}
</RecordTableBodyContextProvider>
);
};

View File

@ -0,0 +1,99 @@
import { RecordTableBodyContextProvider } from '@/object-record/record-table/contexts/RecordTableBodyContext';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useHandleContainerMouseEnter } from '@/object-record/record-table/hooks/internal/useHandleContainerMouseEnter';
import { useUpsertTableRecordInGroup } from '@/object-record/record-table/hooks/internal/useUpsertTableRecordInGroup';
import { useRecordTableMoveFocus } from '@/object-record/record-table/hooks/useRecordTableMoveFocus';
import { useCloseRecordTableCellInGroup } from '@/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellInGroup';
import { useMoveSoftFocusToCellOnHoverV2 } from '@/object-record/record-table/record-table-cell/hooks/useMoveSoftFocusToCellOnHoverV2';
import {
OpenTableCellArgs,
useOpenRecordTableCellV2,
} from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
import { useTriggerActionMenuDropdown } from '@/object-record/record-table/record-table-cell/hooks/useTriggerActionMenuDropdown';
import { MoveFocusDirection } from '@/object-record/record-table/types/MoveFocusDirection';
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
import { ReactNode } from 'react';
type RecordTableRecordGroupBodyContextProviderProps = {
recordGroupId: string;
children?: ReactNode;
};
export const RecordTableRecordGroupBodyContextProvider = ({
recordGroupId,
children,
}: RecordTableRecordGroupBodyContextProviderProps) => {
const { recordTableId } = useRecordTableContextOrThrow();
const { upsertTableRecordInGroup } =
useUpsertTableRecordInGroup(recordGroupId);
const handleupsertTableRecordInGroup = ({
persistField,
recordId,
fieldName,
}: {
persistField: () => void;
recordId: string;
fieldName: string;
}) => {
upsertTableRecordInGroup(persistField, recordId, fieldName);
};
const { openTableCell } = useOpenRecordTableCellV2(recordTableId);
const handleOpenTableCell = (args: OpenTableCellArgs) => {
openTableCell(args);
};
const { moveFocus } = useRecordTableMoveFocus(recordTableId);
const handleMoveFocus = (direction: MoveFocusDirection) => {
moveFocus(direction);
};
const { closeTableCellInGroup } =
useCloseRecordTableCellInGroup(recordGroupId);
const handlecloseTableCellInGroup = () => {
closeTableCellInGroup();
};
const { moveSoftFocusToCell } =
useMoveSoftFocusToCellOnHoverV2(recordTableId);
const handleMoveSoftFocusToCell = (cellPosition: TableCellPosition) => {
moveSoftFocusToCell(cellPosition);
};
const { triggerActionMenuDropdown } = useTriggerActionMenuDropdown({
recordTableId,
});
const handleActionMenuDropdown = (
event: React.MouseEvent,
recordId: string,
) => {
triggerActionMenuDropdown(event, recordId);
};
const { handleContainerMouseEnter } = useHandleContainerMouseEnter({
recordTableId,
});
return (
<RecordTableBodyContextProvider
value={{
onUpsertRecord: handleupsertTableRecordInGroup,
onOpenTableCell: handleOpenTableCell,
onMoveFocus: handleMoveFocus,
onCloseTableCell: handlecloseTableCellInGroup,
onMoveSoftFocusToCell: handleMoveSoftFocusToCell,
onActionMenuDropdownOpened: handleActionMenuDropdown,
onCellMouseEnter: handleContainerMouseEnter,
}}
>
{children}
</RecordTableBodyContextProvider>
);
};

View File

@ -1,7 +1,10 @@
import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId'; import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState'; import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector'; import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
import { RecordTablePendingRecordGroupRow } from '@/object-record/record-table/record-table-row/components/RecordTablePendingRecordGroupRow';
import { RecordTableRow } from '@/object-record/record-table/record-table-row/components/RecordTableRow'; import { RecordTableRow } from '@/object-record/record-table/record-table-row/components/RecordTableRow';
import { RecordTableRecordGroupSectionAddNew } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupSectionAddNew';
import { RecordTableRecordGroupSectionLoadMore } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupSectionLoadMore';
import { isRecordGroupTableSectionToggledComponentState } from '@/object-record/record-table/record-table-section/states/isRecordGroupTableSectionToggledComponentState'; import { isRecordGroupTableSectionToggledComponentState } from '@/object-record/record-table/record-table-section/states/isRecordGroupTableSectionToggledComponentState';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2'; import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
@ -30,24 +33,32 @@ export const RecordTableRecordGroupRows = () => {
[allRecordIds], [allRecordIds],
); );
if (!isRecordGroupTableSectionToggled) {
return null;
}
return ( return (
isRecordGroupTableSectionToggled && <>
recordIdsByGroup.map((recordId, rowIndexInGroup) => { {recordIdsByGroup.map((recordId, rowIndexInGroup) => {
const rowIndex = rowIndexMap.get(recordId); const rowIndex = rowIndexMap.get(recordId);
if (!isDefined(rowIndex)) { if (!isDefined(rowIndex)) {
return null; return null;
} }
return ( return (
<RecordTableRow <RecordTableRow
key={recordId} key={recordId}
recordId={recordId} recordId={recordId}
rowIndexForFocus={rowIndex} rowIndexForFocus={rowIndex}
rowIndexForDrag={rowIndexInGroup} rowIndexForDrag={rowIndexInGroup}
isPendingRow={!isRecordGroupTableSectionToggled} isPendingRow={!isRecordGroupTableSectionToggled}
/> />
); );
}) })}
<RecordTableRecordGroupSectionLoadMore />
<RecordTablePendingRecordGroupRow />
<RecordTableRecordGroupSectionAddNew />
</>
); );
}; };

View File

@ -16,6 +16,8 @@ import { RecordUpdateContext } from '../contexts/EntityUpdateMutationHookContext
import { useRecordTable } from '../hooks/useRecordTable'; import { useRecordTable } from '../hooks/useRecordTable';
import { ActionBarHotkeyScope } from '@/action-menu/types/ActionBarHotKeyScope'; import { ActionBarHotkeyScope } from '@/action-menu/types/ActionBarHotKeyScope';
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
@ -96,27 +98,33 @@ export const RecordTableWithWrappers = ({
); );
return ( return (
<EntityDeleteContext.Provider value={deleteOneRecord}> <RecordTableComponentInstance
<ScrollWrapper recordTableId={recordTableId}
enableXScroll={isScrollEnabledForRecordTable.enableXScroll} onColumnsChange={handleColumnsChange}
enableYScroll={isScrollEnabledForRecordTable.enableYScroll} >
contextProviderName="recordTableWithWrappers" <RecordTableContextProvider
recordTableId={recordTableId}
viewBarId={viewBarId}
objectNameSingular={objectNameSingular}
> >
<RecordUpdateContext.Provider value={updateRecordMutation}> <EntityDeleteContext.Provider value={deleteOneRecord}>
<StyledTableWithHeader> <ScrollWrapper
<StyledTableContainer> enableXScroll={isScrollEnabledForRecordTable.enableXScroll}
<StyledTableInternalContainer> enableYScroll={isScrollEnabledForRecordTable.enableYScroll}
<RecordTable contextProviderName="recordTableWithWrappers"
viewBarId={viewBarId} >
recordTableId={recordTableId} <RecordUpdateContext.Provider value={updateRecordMutation}>
objectNameSingular={objectNameSingular} <StyledTableWithHeader>
onColumnsChange={handleColumnsChange} <StyledTableContainer>
/> <StyledTableInternalContainer>
</StyledTableInternalContainer> <RecordTable />
</StyledTableContainer> </StyledTableInternalContainer>
</StyledTableWithHeader> </StyledTableContainer>
</RecordUpdateContext.Provider> </StyledTableWithHeader>
</ScrollWrapper> </RecordUpdateContext.Provider>
</EntityDeleteContext.Provider> </ScrollWrapper>
</EntityDeleteContext.Provider>
</RecordTableContextProvider>
</RecordTableComponentInstance>
); );
}; };

View File

@ -14,12 +14,13 @@ import {
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance'; import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext'; import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext'; import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { ChipGeneratorsDecorator } from '~/testing/decorators/ChipGeneratorsDecorator'; import { ChipGeneratorsDecorator } from '~/testing/decorators/ChipGeneratorsDecorator';
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator'; import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory'; import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
import { RecordTableBodyContextProvider } from '@/object-record/record-table/contexts/RecordTableBodyContext';
import { RecordTableContextProvider } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableCellFieldContextWrapper } from '@/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextWrapper'; import { RecordTableCellFieldContextWrapper } from '@/object-record/record-table/record-table-cell/components/RecordTableCellFieldContextWrapper';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
import { mockPerformance } from './mock'; import { mockPerformance } from './mock';
@ -61,78 +62,83 @@ const meta: Meta = {
(Story) => { (Story) => {
return ( return (
<RecordFieldValueSelectorContextProvider> <RecordFieldValueSelectorContextProvider>
<RecordTableContext.Provider <RecordTableContextProvider
value={{ value={{
recordTableId: 'recordTableId',
viewBarId: mockPerformance.recordId, viewBarId: mockPerformance.recordId,
objectMetadataItem: mockPerformance.objectMetadataItem as any, objectMetadataItem: mockPerformance.objectMetadataItem as any,
onUpsertRecord: () => {},
onOpenTableCell: () => {},
onMoveFocus: () => {},
onCloseTableCell: () => {},
onMoveSoftFocusToCell: () => {},
onActionMenuDropdownOpened: () => {},
onCellMouseEnter: () => {},
visibleTableColumns: mockPerformance.visibleTableColumns as any, visibleTableColumns: mockPerformance.visibleTableColumns as any,
objectNameSingular: objectNameSingular:
mockPerformance.objectMetadataItem.nameSingular, mockPerformance.objectMetadataItem.nameSingular,
recordTableId: 'recordTableId',
}} }}
> >
<RecordTableComponentInstance <RecordTableComponentInstance
recordTableId="asd" recordTableId="asd"
onColumnsChange={() => {}} onColumnsChange={() => {}}
> >
<RecordTableRowContext.Provider <RecordTableBodyContextProvider
value={{ value={{
objectNameSingular: onUpsertRecord: () => {},
mockPerformance.entityValue.__typename.toLocaleLowerCase(), onOpenTableCell: () => {},
recordId: mockPerformance.recordId, onMoveFocus: () => {},
rowIndex: 0, onCloseTableCell: () => {},
pathToShowPage: onMoveSoftFocusToCell: () => {},
getBasePathToShowPage({ onActionMenuDropdownOpened: () => {},
objectNameSingular: onCellMouseEnter: () => {},
mockPerformance.entityValue.__typename.toLocaleLowerCase(),
}) + mockPerformance.recordId,
isSelected: false,
isDragging: false,
dragHandleProps: null,
inView: true,
isPendingRow: false,
}} }}
> >
<RecordTableCellContext.Provider <RecordTableRowContext.Provider
value={{ value={{
columnDefinition: mockPerformance.fieldDefinition, objectNameSingular:
columnIndex: 0, mockPerformance.entityValue.__typename.toLocaleLowerCase(),
cellPosition: { row: 0, column: 0 }, recordId: mockPerformance.recordId,
hasSoftFocus: false, rowIndex: 0,
isInEditMode: false, pathToShowPage:
getBasePathToShowPage({
objectNameSingular:
mockPerformance.entityValue.__typename.toLocaleLowerCase(),
}) + mockPerformance.recordId,
isSelected: false,
isDragging: false,
dragHandleProps: null,
inView: true,
isPendingRow: false,
}} }}
> >
<FieldContext.Provider <RecordTableCellContext.Provider
value={{ value={{
recordId: mockPerformance.recordId, columnDefinition: mockPerformance.fieldDefinition,
basePathToShowPage: '/object-record/', columnIndex: 0,
isLabelIdentifier: false, cellPosition: { row: 0, column: 0 },
fieldDefinition: { hasSoftFocus: false,
...mockPerformance.fieldDefinition, isInEditMode: false,
},
hotkeyScope: 'hotkey-scope',
}} }}
> >
<RelationFieldValueSetterEffect /> <FieldContext.Provider
<table> value={{
<tbody> recordId: mockPerformance.recordId,
<tr> basePathToShowPage: '/object-record/',
<Story /> isLabelIdentifier: false,
</tr> fieldDefinition: {
</tbody> ...mockPerformance.fieldDefinition,
</table> },
</FieldContext.Provider> hotkeyScope: 'hotkey-scope',
</RecordTableCellContext.Provider> }}
</RecordTableRowContext.Provider> >
<RelationFieldValueSetterEffect />
<table>
<tbody>
<tr>
<Story />
</tr>
</tbody>
</table>
</FieldContext.Provider>
</RecordTableCellContext.Provider>
</RecordTableRowContext.Provider>
</RecordTableBodyContextProvider>
</RecordTableComponentInstance> </RecordTableComponentInstance>
</RecordTableContext.Provider> </RecordTableContextProvider>
</RecordFieldValueSelectorContextProvider> </RecordFieldValueSelectorContextProvider>
); );
}, },

View File

@ -0,0 +1,36 @@
import React from 'react';
import { HandleContainerMouseEnterArgs } from '@/object-record/record-table/hooks/internal/useHandleContainerMouseEnter';
import { OpenTableCellArgs } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
import { MoveFocusDirection } from '@/object-record/record-table/types/MoveFocusDirection';
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
import { createRequiredContext } from '~/utils/createRequiredContext';
export type RecordTableBodyContextProps = {
recordGroupId?: string;
onUpsertRecord: ({
persistField,
recordId,
fieldName,
}: {
persistField: () => void;
recordId: string;
fieldName: string;
}) => void;
onOpenTableCell: (args: OpenTableCellArgs) => void;
onMoveFocus: (direction: MoveFocusDirection) => void;
onCloseTableCell: () => void;
onMoveSoftFocusToCell: (cellPosition: TableCellPosition) => void;
onActionMenuDropdownOpened: (
event: React.MouseEvent,
recordId: string,
) => void;
onCellMouseEnter: (args: HandleContainerMouseEnterArgs) => void;
};
export const [
RecordTableBodyContextProvider,
useRecordTableBodyContextOrThrow,
] = createRequiredContext<RecordTableBodyContextProps>(
'RecordTableBodyContext',
);

View File

@ -1,39 +1,15 @@
import React, { createContext } from 'react';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { HandleContainerMouseEnterArgs } from '@/object-record/record-table/hooks/internal/useHandleContainerMouseEnter';
import { OpenTableCellArgs } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
import { MoveFocusDirection } from '@/object-record/record-table/types/MoveFocusDirection'; import { createRequiredContext } from '~/utils/createRequiredContext';
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
export type RecordTableContextProps = { export type RecordTableContextValue = {
viewBarId: string;
objectMetadataItem: ObjectMetadataItem;
onUpsertRecord: ({
persistField,
recordId,
fieldName,
}: {
persistField: () => void;
recordId: string;
fieldName: string;
}) => void;
onOpenTableCell: (args: OpenTableCellArgs) => void;
onMoveFocus: (direction: MoveFocusDirection) => void;
onCloseTableCell: () => void;
onMoveSoftFocusToCell: (cellPosition: TableCellPosition) => void;
onActionMenuDropdownOpened: (
event: React.MouseEvent,
recordId: string,
) => void;
onCellMouseEnter: (args: HandleContainerMouseEnterArgs) => void;
visibleTableColumns: ColumnDefinition<FieldMetadata>[];
recordTableId: string; recordTableId: string;
viewBarId: string;
objectNameSingular: string; objectNameSingular: string;
objectMetadataItem: ObjectMetadataItem;
visibleTableColumns: ColumnDefinition<FieldMetadata>[];
}; };
export const RecordTableContext = createContext<RecordTableContextProps>( export const [RecordTableContextProvider, useRecordTableContextOrThrow] =
{} as RecordTableContextProps, createRequiredContext<RecordTableContextValue>('RecordTableContext');
);

View File

@ -11,6 +11,7 @@ export type RecordTableRowContextProps = {
isDragging: boolean; isDragging: boolean;
dragHandleProps: DraggableProvidedDragHandleProps | null; dragHandleProps: DraggableProvidedDragHandleProps | null;
inView?: boolean; inView?: boolean;
isDragDisabled?: boolean;
}; };
export const RecordTableRowContext = createContext<RecordTableRowContextProps>( export const RecordTableRowContext = createContext<RecordTableRowContextProps>(

View File

@ -1,16 +1,15 @@
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableEmptyStateNoRecordAtAll } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordAtAll'; import { RecordTableEmptyStateNoRecordAtAll } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordAtAll';
import { RecordTableEmptyStateNoRecordFoundForFilter } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordFoundForFilter'; import { RecordTableEmptyStateNoRecordFoundForFilter } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordFoundForFilter';
import { RecordTableEmptyStateRemote } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateRemote'; import { RecordTableEmptyStateRemote } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateRemote';
import { RecordTableEmptyStateSoftDelete } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete'; import { RecordTableEmptyStateSoftDelete } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete';
import { isSoftDeleteFilterActiveComponentState } from '@/object-record/record-table/states/isSoftDeleteFilterActiveComponentState'; import { isSoftDeleteFilterActiveComponentState } from '@/object-record/record-table/states/isSoftDeleteFilterActiveComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useContext } from 'react';
export const RecordTableEmptyState = () => { export const RecordTableEmptyState = () => {
const { objectNameSingular, recordTableId, objectMetadataItem } = const { recordTableId, objectNameSingular, objectMetadataItem } =
useContext(RecordTableContext); useRecordTableContextOrThrow();
const { totalCount } = useFindManyRecords({ objectNameSingular, limit: 1 }); const { totalCount } = useFindManyRecords({ objectNameSingular, limit: 1 });
const noRecordAtAll = totalCount === 0; const noRecordAtAll = totalCount === 0;

View File

@ -1,6 +1,5 @@
import { isObjectMetadataReadOnly } from '@/object-metadata/utils/isObjectMetadataReadOnly'; import { isObjectMetadataReadOnly } from '@/object-metadata/utils/isObjectMetadataReadOnly';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useContext } from 'react';
import { import {
AnimatedPlaceholder, AnimatedPlaceholder,
AnimatedPlaceholderEmptyContainer, AnimatedPlaceholderEmptyContainer,
@ -29,7 +28,7 @@ export const RecordTableEmptyStateDisplay = ({
subTitle, subTitle,
title, title,
}: RecordTableEmptyStateDisplayProps) => { }: RecordTableEmptyStateDisplayProps) => {
const { objectMetadataItem } = useContext(RecordTableContext); const { objectMetadataItem } = useRecordTableContextOrThrow();
const isReadOnly = isObjectMetadataReadOnly(objectMetadataItem); const isReadOnly = isObjectMetadataReadOnly(objectMetadataItem);
return ( return (

View File

@ -1,15 +1,14 @@
import { IconPlus } from 'twenty-ui'; import { IconPlus } from 'twenty-ui';
import { useObjectLabel } from '@/object-metadata/hooks/useObjectLabel'; import { useObjectLabel } from '@/object-metadata/hooks/useObjectLabel';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay'; import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay';
import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords'; import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords';
import { useContext } from 'react';
export const RecordTableEmptyStateNoRecordAtAll = () => { export const RecordTableEmptyStateNoRecordAtAll = () => {
const { createNewTableRecord } = useCreateNewTableRecord(); const { objectMetadataItem, recordTableId } = useRecordTableContextOrThrow();
const { objectMetadataItem } = useContext(RecordTableContext); const { createNewTableRecord } = useCreateNewTableRecord(recordTableId);
const handleButtonClick = () => { const handleButtonClick = () => {
createNewTableRecord(); createNewTableRecord();

View File

@ -1,15 +1,14 @@
import { IconPlus } from 'twenty-ui'; import { IconPlus } from 'twenty-ui';
import { useObjectLabel } from '@/object-metadata/hooks/useObjectLabel'; import { useObjectLabel } from '@/object-metadata/hooks/useObjectLabel';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay'; import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay';
import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords'; import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords';
import { useContext } from 'react';
export const RecordTableEmptyStateNoRecordFoundForFilter = () => { export const RecordTableEmptyStateNoRecordFoundForFilter = () => {
const { createNewTableRecord } = useCreateNewTableRecord(); const { recordTableId, objectMetadataItem } = useRecordTableContextOrThrow();
const { objectMetadataItem } = useContext(RecordTableContext); const { createNewTableRecord } = useCreateNewTableRecord(recordTableId);
const handleButtonClick = () => { const handleButtonClick = () => {
createNewTableRecord(); createNewTableRecord();

View File

@ -2,16 +2,15 @@ import { IconFilterOff } from 'twenty-ui';
import { useObjectLabel } from '@/object-metadata/hooks/useObjectLabel'; import { useObjectLabel } from '@/object-metadata/hooks/useObjectLabel';
import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter'; import { useHandleToggleTrashColumnFilter } from '@/object-record/record-index/hooks/useHandleToggleTrashColumnFilter';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay'; import { RecordTableEmptyStateDisplay } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateDisplay';
import { tableFiltersComponentState } from '@/object-record/record-table/states/tableFiltersComponentState'; import { tableFiltersComponentState } from '@/object-record/record-table/states/tableFiltersComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useDeleteCombinedViewFilters } from '@/views/hooks/useDeleteCombinedViewFilters'; import { useDeleteCombinedViewFilters } from '@/views/hooks/useDeleteCombinedViewFilters';
import { useContext } from 'react';
export const RecordTableEmptyStateSoftDelete = () => { export const RecordTableEmptyStateSoftDelete = () => {
const { objectMetadataItem, objectNameSingular, recordTableId } = const { objectMetadataItem, objectNameSingular, recordTableId } =
useContext(RecordTableContext); useRecordTableContextOrThrow();
const { deleteCombinedViewFilter } = const { deleteCombinedViewFilter } =
useDeleteCombinedViewFilters(recordTableId); useDeleteCombinedViewFilters(recordTableId);

View File

@ -1,5 +1,5 @@
import { act, renderHook } from '@testing-library/react'; import { renderHook } from '@testing-library/react';
import { ReactNode } from 'react'; import { ReactNode, act } from 'react';
import { RecoilRoot } from 'recoil'; import { RecoilRoot } from 'recoil';
import { createState } from 'twenty-ui'; import { createState } from 'twenty-ui';
@ -9,12 +9,16 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { textfieldDefinition } from '@/object-record/record-field/__mocks__/fieldDefinitions'; import { textfieldDefinition } from '@/object-record/record-field/__mocks__/fieldDefinitions';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { useUpsertRecord } from '@/object-record/record-table/record-table-cell/hooks/useUpsertRecord'; import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider';
import { useUpsertTableRecordInGroup } from '@/object-record/record-table/hooks/internal/useUpsertTableRecordInGroup';
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext'; import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems'; import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
const draftValue = 'updated Name'; const draftValue = 'updated Name';
const recordGroupId = 'recordGroupId';
// Todo refactor this test to inject the states in a cleaner way instead of mocking hooks // Todo refactor this test to inject the states in a cleaner way instead of mocking hooks
// (this is not easy to maintain while refactoring) // (this is not easy to maintain while refactoring)
@ -37,8 +41,11 @@ jest.mock(
}), }),
); );
const pendingRecordIdState = createState<string | null>({ const recordTablePendingRecordIdByGroupComponentFamilyState = createFamilyState<
key: 'pendingRecordIdState', string | null,
string
>({
key: 'recordTablePendingRecordIdByGroupComponentFamilyState',
defaultValue: null, defaultValue: null,
}); });
@ -60,46 +67,64 @@ const Wrapper = ({
<RecoilRoot <RecoilRoot
initializeState={(snapshot) => { initializeState={(snapshot) => {
snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems); snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems);
snapshot.set(pendingRecordIdState, pendingRecordIdMockedValue); snapshot.set(
recordTablePendingRecordIdByGroupComponentFamilyState(recordGroupId),
pendingRecordIdMockedValue,
);
snapshot.set(draftValueState, draftValueMockedValue); snapshot.set(draftValueState, draftValueMockedValue);
}} }}
> >
<ViewComponentInstanceContext.Provider <RecordTableContextProvider
value={{ instanceId: CoreObjectNamePlural.Person }} recordTableId="recordTableId"
objectNameSingular={CoreObjectNameSingular.Person}
viewBarId="viewBarId"
> >
<FieldContext.Provider <RecordTableComponentInstanceContext.Provider
value={{ value={{
recordId: 'recordId', instanceId: CoreObjectNamePlural.Person,
fieldDefinition: { onColumnsChange: jest.fn(),
...textfieldDefinition,
metadata: {
...textfieldDefinition.metadata,
objectMetadataNameSingular: CoreObjectNameSingular.Person,
},
},
hotkeyScope: TableHotkeyScope.Table,
isLabelIdentifier: false,
}} }}
> >
{children} <ViewComponentInstanceContext.Provider
</FieldContext.Provider> value={{ instanceId: CoreObjectNamePlural.Person }}
</ViewComponentInstanceContext.Provider> >
<FieldContext.Provider
value={{
recordId: 'recordId',
fieldDefinition: {
...textfieldDefinition,
metadata: {
...textfieldDefinition.metadata,
objectMetadataNameSingular: CoreObjectNameSingular.Person,
},
},
hotkeyScope: TableHotkeyScope.Table,
isLabelIdentifier: false,
}}
>
{children}
</FieldContext.Provider>
</ViewComponentInstanceContext.Provider>
</RecordTableComponentInstanceContext.Provider>
</RecordTableContextProvider>
</RecoilRoot> </RecoilRoot>
); );
describe('useUpsertRecord', () => { describe('useUpsertTableRecordInGroup', () => {
beforeEach(async () => { beforeEach(async () => {
createOneRecordMock.mockClear(); createOneRecordMock.mockClear();
updateOneRecordMock.mockClear(); updateOneRecordMock.mockClear();
}); });
it('calls update record if there is no pending record', async () => { it('calls update record if there is no pending record', async () => {
const { result } = renderHook( /**
() => * {
useUpsertRecord({
objectNameSingular: 'person', objectNameSingular: 'person',
recordTableId: 'recordTableId', recordTableId: 'recordTableId',
}), }
*/
const { result } = renderHook(
() => useUpsertTableRecordInGroup(recordGroupId),
{ {
wrapper: ({ children }) => wrapper: ({ children }) =>
Wrapper({ Wrapper({
@ -111,7 +136,7 @@ describe('useUpsertRecord', () => {
); );
await act(async () => { await act(async () => {
await result.current.upsertRecord( await result.current.upsertTableRecordInGroup(
updateOneRecordMock, updateOneRecordMock,
'recordId', 'recordId',
'name', 'name',
@ -124,11 +149,7 @@ describe('useUpsertRecord', () => {
it('calls update record if pending record is empty', async () => { it('calls update record if pending record is empty', async () => {
const { result } = renderHook( const { result } = renderHook(
() => () => useUpsertTableRecordInGroup(recordGroupId),
useUpsertRecord({
objectNameSingular: 'person',
recordTableId: 'recordTableId',
}),
{ {
wrapper: ({ children }) => wrapper: ({ children }) =>
Wrapper({ Wrapper({
@ -140,7 +161,7 @@ describe('useUpsertRecord', () => {
); );
await act(async () => { await act(async () => {
await result.current.upsertRecord( await result.current.upsertTableRecordInGroup(
updateOneRecordMock, updateOneRecordMock,
'recordId', 'recordId',
'name', 'name',

View File

@ -0,0 +1,160 @@
import { renderHook } from '@testing-library/react';
import { ReactNode, act } from 'react';
import { RecoilRoot } from 'recoil';
import { createState } from 'twenty-ui';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { textfieldDefinition } from '@/object-record/record-field/__mocks__/fieldDefinitions';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider';
import { useUpsertTableRecordNoGroup } from '@/object-record/record-table/hooks/internal/useUpsertTableRecordNoGroup';
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
const draftValue = 'updated Name';
// Todo refactor this test to inject the states in a cleaner way instead of mocking hooks
// (this is not easy to maintain while refactoring)
jest.mock('@/object-record/hooks/useCreateOneRecord', () => ({
__esModule: true,
useCreateOneRecord: jest.fn(),
}));
const draftValueState = createState<string | null>({
key: 'draftValueState',
defaultValue: null,
});
jest.mock(
'@/object-record/record-field/hooks/internal/useRecordFieldInputStates',
() => ({
__esModule: true,
useRecordFieldInputStates: jest.fn(() => ({
getDraftValueSelector: () => draftValueState,
})),
}),
);
const pendingRecordIdState = createState<string | null>({
key: 'pendingRecordIdState',
defaultValue: null,
});
const createOneRecordMock = jest.fn();
const updateOneRecordMock = jest.fn();
(useCreateOneRecord as jest.Mock).mockReturnValue({
createOneRecord: createOneRecordMock,
});
const Wrapper = ({
children,
pendingRecordIdMockedValue,
draftValueMockedValue,
}: {
children: ReactNode;
pendingRecordIdMockedValue: string | null;
draftValueMockedValue: string | null;
}) => (
<RecoilRoot
initializeState={(snapshot) => {
snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems);
snapshot.set(pendingRecordIdState, pendingRecordIdMockedValue);
snapshot.set(draftValueState, draftValueMockedValue);
}}
>
<RecordTableContextProvider
recordTableId="recordTableId"
objectNameSingular={CoreObjectNameSingular.Person}
viewBarId="viewBarId"
>
<RecordTableComponentInstanceContext.Provider
value={{
instanceId: CoreObjectNamePlural.Person,
onColumnsChange: jest.fn(),
}}
>
<ViewComponentInstanceContext.Provider
value={{ instanceId: CoreObjectNamePlural.Person }}
>
<FieldContext.Provider
value={{
recordId: 'recordId',
fieldDefinition: {
...textfieldDefinition,
metadata: {
...textfieldDefinition.metadata,
objectMetadataNameSingular: CoreObjectNameSingular.Person,
},
},
hotkeyScope: TableHotkeyScope.Table,
isLabelIdentifier: false,
}}
>
{children}
</FieldContext.Provider>
</ViewComponentInstanceContext.Provider>
</RecordTableComponentInstanceContext.Provider>
</RecordTableContextProvider>
</RecoilRoot>
);
describe('useUpsertTableRecordNoGroup', () => {
beforeEach(async () => {
createOneRecordMock.mockClear();
updateOneRecordMock.mockClear();
});
it('calls update record if there is no pending record', async () => {
/**
* {
objectNameSingular: 'person',
recordTableId: 'recordTableId',
}
*/
const { result } = renderHook(() => useUpsertTableRecordNoGroup(), {
wrapper: ({ children }) =>
Wrapper({
pendingRecordIdMockedValue: null,
draftValueMockedValue: null,
children,
}),
});
await act(async () => {
await result.current.upsertTableRecordNoGroup(
updateOneRecordMock,
'recordId',
'name',
);
});
expect(createOneRecordMock).not.toHaveBeenCalled();
expect(updateOneRecordMock).toHaveBeenCalled();
});
it('calls update record if pending record is empty', async () => {
const { result } = renderHook(() => useUpsertTableRecordNoGroup(), {
wrapper: ({ children }) =>
Wrapper({
pendingRecordIdMockedValue: null,
draftValueMockedValue: draftValue,
children,
}),
});
await act(async () => {
await result.current.upsertTableRecordNoGroup(
updateOneRecordMock,
'recordId',
'name',
);
});
expect(createOneRecordMock).not.toHaveBeenCalled();
expect(updateOneRecordMock).toHaveBeenCalled();
});
});

View File

@ -0,0 +1,87 @@
import { useRecoilCallback } from 'recoil';
import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { recordFieldInputDraftValueComponentSelector } from '@/object-record/record-field/states/selectors/recordFieldInputDraftValueComponentSelector';
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState';
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { extractComponentSelector } from '@/ui/utilities/state/component-state/utils/extractComponentSelector';
import { isDefined } from '~/utils/isDefined';
export const useUpsertTableRecordInGroup = (recordGroupId: string) => {
const { objectMetadataItem, objectNameSingular } =
useRecordTableContextOrThrow();
const { createOneRecord } = useCreateOneRecord({
objectNameSingular,
shouldMatchRootQueryFilter: true,
});
const recordTablePendingRecordIdByGroupFamilyState =
useRecoilComponentCallbackStateV2(
recordTablePendingRecordIdByGroupComponentFamilyState,
);
const upsertTableRecordInGroup = useRecoilCallback(
({ snapshot }) =>
(persistField: () => void, recordId: string, fieldName: string) => {
const labelIdentifierFieldMetadataItem =
getLabelIdentifierFieldMetadataItem(objectMetadataItem);
const fieldScopeId = getScopeIdFromComponentId(
`${recordId}-${fieldName}`,
);
const draftValueSelector = extractComponentSelector(
recordFieldInputDraftValueComponentSelector,
fieldScopeId,
);
const draftValue = getSnapshotValue(snapshot, draftValueSelector());
// We're in a record group
const recordTablePendingRecordId = getSnapshotValue(
snapshot,
recordTablePendingRecordIdByGroupFamilyState(recordGroupId),
);
const recordGroupDefinition = getSnapshotValue(
snapshot,
recordGroupDefinitionFamilyState(recordGroupId),
);
const recordGroupFieldMetadataItem = objectMetadataItem.fields.find(
(fieldMetadata) =>
fieldMetadata.id === recordGroupDefinition?.fieldMetadataId,
);
if (
isDefined(recordTablePendingRecordId) &&
isDefined(recordGroupDefinition) &&
isDefined(recordGroupFieldMetadataItem) &&
isDefined(draftValue)
) {
createOneRecord({
id: recordTablePendingRecordId,
[labelIdentifierFieldMetadataItem?.name ?? 'name']: draftValue,
[recordGroupFieldMetadataItem.name]: recordGroupDefinition.value,
position: 'first',
});
} else if (!recordTablePendingRecordId) {
persistField();
}
},
[
createOneRecord,
objectMetadataItem,
recordGroupId,
recordTablePendingRecordIdByGroupFamilyState,
],
);
return { upsertTableRecordInGroup };
};

View File

@ -1,32 +1,22 @@
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem'; import { getLabelIdentifierFieldMetadataItem } from '@/object-metadata/utils/getLabelIdentifierFieldMetadataItem';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { recordFieldInputDraftValueComponentSelector } from '@/object-record/record-field/states/selectors/recordFieldInputDraftValueComponentSelector'; import { recordFieldInputDraftValueComponentSelector } from '@/object-record/record-field/states/selectors/recordFieldInputDraftValueComponentSelector';
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState'; import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState';
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { extractComponentSelector } from '@/ui/utilities/state/component-state/utils/extractComponentSelector'; import { extractComponentSelector } from '@/ui/utilities/state/component-state/utils/extractComponentSelector';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
export const useUpsertRecord = ({ export const useUpsertTableRecordNoGroup = () => {
objectNameSingular, const { objectMetadataItem, objectNameSingular, recordTableId } =
recordTableId, useRecordTableContextOrThrow();
}: {
objectNameSingular: string;
recordTableId: string;
}) => {
const hasRecordGroups = useRecoilComponentValueV2(
hasRecordGroupsComponentSelector,
);
const { createOneRecord } = useCreateOneRecord({ const { createOneRecord } = useCreateOneRecord({
objectNameSingular, objectNameSingular,
shouldMatchRootQueryFilter: hasRecordGroups,
}); });
const recordTablePendingRecordIdState = useRecoilComponentCallbackStateV2( const recordTablePendingRecordIdState = useRecoilComponentCallbackStateV2(
@ -34,28 +24,12 @@ export const useUpsertRecord = ({
recordTableId, recordTableId,
); );
const upsertRecord = useRecoilCallback( const upsertTableRecordNoGroup = useRecoilCallback(
({ snapshot }) => ({ snapshot }) =>
(persistField: () => void, recordId: string, fieldName: string) => { (persistField: () => void, recordId: string, fieldName: string) => {
const objectMetadataItems = snapshot
.getLoadable(objectMetadataItemsState)
.getValue();
const foundObjectMetadataItem = objectMetadataItems.find(
(item) => item.nameSingular === objectNameSingular,
);
if (!foundObjectMetadataItem) {
throw new Error('Object metadata item cannot be found');
}
const labelIdentifierFieldMetadataItem = const labelIdentifierFieldMetadataItem =
getLabelIdentifierFieldMetadataItem(foundObjectMetadataItem); getLabelIdentifierFieldMetadataItem(objectMetadataItem);
const recordTablePendingRecordId = getSnapshotValue(
snapshot,
recordTablePendingRecordIdState,
);
const fieldScopeId = getScopeIdFromComponentId( const fieldScopeId = getScopeIdFromComponentId(
`${recordId}-${fieldName}`, `${recordId}-${fieldName}`,
); );
@ -67,6 +41,11 @@ export const useUpsertRecord = ({
const draftValue = getSnapshotValue(snapshot, draftValueSelector()); const draftValue = getSnapshotValue(snapshot, draftValueSelector());
const recordTablePendingRecordId = getSnapshotValue(
snapshot,
recordTablePendingRecordIdState,
);
if (isDefined(recordTablePendingRecordId) && isDefined(draftValue)) { if (isDefined(recordTablePendingRecordId) && isDefined(draftValue)) {
createOneRecord({ createOneRecord({
id: recordTablePendingRecordId, id: recordTablePendingRecordId,
@ -77,8 +56,8 @@ export const useUpsertRecord = ({
persistField(); persistField();
} }
}, },
[createOneRecord, objectNameSingular, recordTablePendingRecordIdState], [createOneRecord, objectMetadataItem, recordTablePendingRecordIdState],
); );
return { upsertRecord }; return { upsertTableRecordNoGroup };
}; };

View File

@ -0,0 +1,68 @@
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
import { useSelectedTableCellEditMode } from '@/object-record/record-table/record-table-cell/hooks/useSelectedTableCellEditMode';
import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState';
import { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropdownFocusIdForRecordField';
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { useRecoilCallback } from 'recoil';
import { v4 } from 'uuid';
import { isDefined } from '~/utils/isDefined';
export const useCreateNewTableRecordInGroup = () => {
const { recordIndexId, objectMetadataItem } = useRecordIndexContextOrThrow();
const { setSelectedTableCellEditMode } = useSelectedTableCellEditMode({
scopeId: recordIndexId,
});
const setHotkeyScope = useSetHotkeyScope();
const recordTablePendingRecordIdByGroupFamilyState =
useRecoilComponentCallbackStateV2(
recordTablePendingRecordIdByGroupComponentFamilyState,
recordIndexId,
);
const { setActiveDropdownFocusIdAndMemorizePrevious } =
useSetActiveDropdownFocusIdAndMemorizePrevious();
const createNewTableRecordInGroup = useRecoilCallback(
({ set }) =>
(recordGroupId: string) => {
const recordId = v4();
set(
recordTablePendingRecordIdByGroupFamilyState(recordGroupId),
recordId,
);
setSelectedTableCellEditMode(-1, 0);
setHotkeyScope(
DEFAULT_CELL_SCOPE.scope,
DEFAULT_CELL_SCOPE.customScopes,
);
if (isDefined(objectMetadataItem.labelIdentifierFieldMetadataId)) {
setActiveDropdownFocusIdAndMemorizePrevious(
getDropdownFocusIdForRecordField(
recordId,
objectMetadataItem.labelIdentifierFieldMetadataId,
'table-cell',
),
);
}
},
[
objectMetadataItem,
recordTablePendingRecordIdByGroupFamilyState,
setActiveDropdownFocusIdAndMemorizePrevious,
setHotkeyScope,
setSelectedTableCellEditMode,
],
);
return {
createNewTableRecordInGroup,
};
};

View File

@ -1,33 +1,94 @@
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2'; import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
import { useSelectedTableCellEditMode } from '@/object-record/record-table/record-table-cell/hooks/useSelectedTableCellEditMode'; import { useSelectedTableCellEditMode } from '@/object-record/record-table/record-table-cell/hooks/useSelectedTableCellEditMode';
import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState';
import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState';
import { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropdownFocusIdForRecordField';
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { useContext } from 'react'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { useRecoilCallback } from 'recoil';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { isDefined } from '~/utils/isDefined';
export const useCreateNewTableRecord = (recordTableIdFromProps?: string) => { export const useCreateNewTableRecord = (recordTableId: string) => {
const { recordTableId } = useContext(RecordTableContext); const { objectMetadataItem } = useRecordIndexContextOrThrow();
const recordTableIdToUse = recordTableIdFromProps ?? recordTableId;
const { setSelectedTableCellEditMode } = useSelectedTableCellEditMode({ const { setSelectedTableCellEditMode } = useSelectedTableCellEditMode({
scopeId: recordTableIdToUse, scopeId: recordTableId,
}); });
const setHotkeyScope = useSetHotkeyScope(); const setHotkeyScope = useSetHotkeyScope();
const { setPendingRecordId } = useRecordTable({ const setPendingRecordId = useSetRecoilComponentStateV2(
recordTableId: recordTableIdToUse, recordTablePendingRecordIdComponentState,
}); recordTableId,
);
const recordTablePendingRecordIdByGroupFamilyState =
useRecoilComponentCallbackStateV2(
recordTablePendingRecordIdByGroupComponentFamilyState,
recordTableId,
);
const { setActiveDropdownFocusIdAndMemorizePrevious } =
useSetActiveDropdownFocusIdAndMemorizePrevious();
const createNewTableRecord = () => { const createNewTableRecord = () => {
setPendingRecordId(v4()); const recordId = v4();
setPendingRecordId(recordId);
setSelectedTableCellEditMode(-1, 0); setSelectedTableCellEditMode(-1, 0);
setHotkeyScope(DEFAULT_CELL_SCOPE.scope, DEFAULT_CELL_SCOPE.customScopes); setHotkeyScope(DEFAULT_CELL_SCOPE.scope, DEFAULT_CELL_SCOPE.customScopes);
if (isDefined(objectMetadataItem.labelIdentifierFieldMetadataId)) {
setActiveDropdownFocusIdAndMemorizePrevious(
getDropdownFocusIdForRecordField(
recordId,
objectMetadataItem.labelIdentifierFieldMetadataId,
'table-cell',
),
);
}
}; };
const createNewTableRecordInGroup = useRecoilCallback(
({ set }) =>
(recordGroupId: string) => {
const recordId = v4();
set(
recordTablePendingRecordIdByGroupFamilyState(recordGroupId),
recordId,
);
setSelectedTableCellEditMode(-1, 0);
setHotkeyScope(
DEFAULT_CELL_SCOPE.scope,
DEFAULT_CELL_SCOPE.customScopes,
);
if (isDefined(objectMetadataItem.labelIdentifierFieldMetadataId)) {
setActiveDropdownFocusIdAndMemorizePrevious(
getDropdownFocusIdForRecordField(
recordId,
objectMetadataItem.labelIdentifierFieldMetadataId,
'table-cell',
),
);
}
},
[
objectMetadataItem,
recordTablePendingRecordIdByGroupFamilyState,
setActiveDropdownFocusIdAndMemorizePrevious,
setHotkeyScope,
setSelectedTableCellEditMode,
],
);
return { return {
createNewTableRecord, createNewTableRecord,
createNewTableRecordInGroup,
}; };
}; };

View File

@ -22,7 +22,6 @@ import { onColumnsChangeComponentState } from '@/object-record/record-table/stat
import { onEntityCountChangeComponentState } from '@/object-record/record-table/states/onEntityCountChangeComponentState'; import { onEntityCountChangeComponentState } from '@/object-record/record-table/states/onEntityCountChangeComponentState';
import { onToggleColumnFilterComponentState } from '@/object-record/record-table/states/onToggleColumnFilterComponentState'; import { onToggleColumnFilterComponentState } from '@/object-record/record-table/states/onToggleColumnFilterComponentState';
import { onToggleColumnSortComponentState } from '@/object-record/record-table/states/onToggleColumnSortComponentState'; import { onToggleColumnSortComponentState } from '@/object-record/record-table/states/onToggleColumnSortComponentState';
import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState';
import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState'; import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
import { tableFiltersComponentState } from '@/object-record/record-table/states/tableFiltersComponentState'; import { tableFiltersComponentState } from '@/object-record/record-table/states/tableFiltersComponentState';
import { tableLastRowVisibleComponentState } from '@/object-record/record-table/states/tableLastRowVisibleComponentState'; import { tableLastRowVisibleComponentState } from '@/object-record/record-table/states/tableLastRowVisibleComponentState';
@ -240,11 +239,6 @@ export const useRecordTable = (props?: useRecordTableProps) => {
const isSomeCellInEditModeState = const isSomeCellInEditModeState =
useGetIsSomeCellInEditModeState(recordTableId); useGetIsSomeCellInEditModeState(recordTableId);
const setPendingRecordId = useSetRecoilComponentStateV2(
recordTablePendingRecordIdComponentState,
recordTableId,
);
return { return {
onColumnsChange, onColumnsChange,
setAvailableTableColumns, setAvailableTableColumns,
@ -272,6 +266,5 @@ export const useRecordTable = (props?: useRecordTableProps) => {
setHasUserSelectedAllRows, setHasUserSelectedAllRows,
setOnToggleColumnFilter, setOnToggleColumnFilter,
setOnToggleColumnSort, setOnToggleColumnSort,
setPendingRecordId,
}; };
}; };

View File

@ -1,12 +1,12 @@
import { DragDropContext, DropResult } from '@hello-pangea/dnd'; import { DragDropContext, DropResult } from '@hello-pangea/dnd';
import { ReactNode, useContext } from 'react'; import { ReactNode } from 'react';
import { useRecoilCallback, useSetRecoilState } from 'recoil'; import { useRecoilCallback, useSetRecoilState } from 'recoil';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { getDraggedRecordPosition } from '@/object-record/record-board/utils/getDraggedRecordPosition'; import { getDraggedRecordPosition } from '@/object-record/record-board/utils/getDraggedRecordPosition';
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector'; import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { isRemoveSortingModalOpenState } from '@/object-record/record-table/states/isRemoveSortingModalOpenState'; import { isRemoveSortingModalOpenState } from '@/object-record/record-table/states/isRemoveSortingModalOpenState';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
@ -18,7 +18,7 @@ export const RecordTableBodyDragDropContextProvider = ({
}: { }: {
children: ReactNode; children: ReactNode;
}) => { }) => {
const { objectNameSingular, recordTableId } = useContext(RecordTableContext); const { objectNameSingular, recordTableId } = useRecordTableContextOrThrow();
const { updateOneRecord: updateOneRow } = useUpdateOneRecord({ const { updateOneRecord: updateOneRow } = useUpdateOneRecord({
objectNameSingular, objectNameSingular,

View File

@ -1,5 +1,5 @@
import { DragDropContext, DropResult } from '@hello-pangea/dnd'; import { DragDropContext, DropResult } from '@hello-pangea/dnd';
import { ReactNode, useContext } from 'react'; import { ReactNode } from 'react';
import { useRecoilCallback, useSetRecoilState } from 'recoil'; import { useRecoilCallback, useSetRecoilState } from 'recoil';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
@ -7,7 +7,7 @@ import { getDraggedRecordPosition } from '@/object-record/record-board/utils/get
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState'; import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState'; import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { isRemoveSortingModalOpenState } from '@/object-record/record-table/states/isRemoveSortingModalOpenState'; import { isRemoveSortingModalOpenState } from '@/object-record/record-table/states/isRemoveSortingModalOpenState';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
@ -20,7 +20,7 @@ export const RecordTableBodyRecordGroupDragDropContextProvider = ({
children: ReactNode; children: ReactNode;
}) => { }) => {
const { objectNameSingular, recordTableId, objectMetadataItem } = const { objectNameSingular, recordTableId, objectMetadataItem } =
useContext(RecordTableContext); useRecordTableContextOrThrow();
const { updateOneRecord: updateOneRow } = useUpdateOneRecord({ const { updateOneRecord: updateOneRow } = useUpdateOneRecord({
objectNameSingular, objectNameSingular,

View File

@ -1,6 +1,7 @@
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId'; import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useLeaveTableFocus } from '@/object-record/record-table/hooks/internal/useLeaveTableFocus'; import { useLeaveTableFocus } from '@/object-record/record-table/hooks/internal/useLeaveTableFocus';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
@ -9,13 +10,13 @@ import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useLis
type RecordTableBodyUnselectEffectProps = { type RecordTableBodyUnselectEffectProps = {
tableBodyRef: React.RefObject<HTMLDivElement>; tableBodyRef: React.RefObject<HTMLDivElement>;
recordTableId: string;
}; };
export const RecordTableBodyUnselectEffect = ({ export const RecordTableBodyUnselectEffect = ({
tableBodyRef, tableBodyRef,
recordTableId,
}: RecordTableBodyUnselectEffectProps) => { }: RecordTableBodyUnselectEffectProps) => {
const { recordTableId } = useRecordTableContextOrThrow();
const leaveTableFocus = useLeaveTableFocus(recordTableId); const leaveTableFocus = useLeaveTableFocus(recordTableId);
const { resetTableRowSelection, useMapKeyboardToSoftFocus } = useRecordTable({ const { resetTableRowSelection, useMapKeyboardToSoftFocus } = useRecordTable({

View File

@ -1,4 +1,5 @@
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector'; import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
import { RecordTableNoRecordGroupBodyContextProvider } from '@/object-record/record-table/components/RecordTableNoRecordGroupBodyContextProvider';
import { RecordTableNoRecordGroupRows } from '@/object-record/record-table/components/RecordTableNoRecordGroupRows'; import { RecordTableNoRecordGroupRows } from '@/object-record/record-table/components/RecordTableNoRecordGroupRows';
import { RecordTableBodyDragDropContextProvider } from '@/object-record/record-table/record-table-body/components/RecordTableBodyDragDropContextProvider'; import { RecordTableBodyDragDropContextProvider } from '@/object-record/record-table/record-table-body/components/RecordTableBodyDragDropContextProvider';
import { RecordTableBodyDroppable } from '@/object-record/record-table/record-table-body/components/RecordTableBodyDroppable'; import { RecordTableBodyDroppable } from '@/object-record/record-table/record-table-body/components/RecordTableBodyDroppable';
@ -21,11 +22,13 @@ export const RecordTableNoRecordGroupBody = () => {
} }
return ( return (
<RecordTableBodyDragDropContextProvider> <RecordTableNoRecordGroupBodyContextProvider>
<RecordTableBodyDroppable> <RecordTableBodyDragDropContextProvider>
<RecordTablePendingRow /> <RecordTableBodyDroppable>
<RecordTableNoRecordGroupRows /> <RecordTablePendingRow />
</RecordTableBodyDroppable> <RecordTableNoRecordGroupRows />
</RecordTableBodyDragDropContextProvider> </RecordTableBodyDroppable>
</RecordTableBodyDragDropContextProvider>
</RecordTableNoRecordGroupBodyContextProvider>
); );
}; };

View File

@ -1,4 +1,4 @@
import { useContext, useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil'; import { useRecoilState, useRecoilValue } from 'recoil';
import { useDebouncedCallback } from 'use-debounce'; import { useDebouncedCallback } from 'use-debounce';
@ -6,7 +6,7 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMembe
import { lastShowPageRecordIdState } from '@/object-record/record-field/states/lastShowPageRecordId'; import { lastShowPageRecordIdState } from '@/object-record/record-field/states/lastShowPageRecordId';
import { useLazyLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLazyLoadRecordIndexTable'; import { useLazyLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLazyLoadRecordIndexTable';
import { ROW_HEIGHT } from '@/object-record/record-table/constants/RowHeight'; import { ROW_HEIGHT } from '@/object-record/record-table/constants/RowHeight';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { hasRecordTableFetchedAllRecordsComponentStateV2 } from '@/object-record/record-table/states/hasRecordTableFetchedAllRecordsComponentStateV2'; import { hasRecordTableFetchedAllRecordsComponentStateV2 } from '@/object-record/record-table/states/hasRecordTableFetchedAllRecordsComponentStateV2';
import { tableEncounteredUnrecoverableErrorComponentState } from '@/object-record/record-table/states/tableEncounteredUnrecoverableErrorComponentState'; import { tableEncounteredUnrecoverableErrorComponentState } from '@/object-record/record-table/states/tableEncounteredUnrecoverableErrorComponentState';
import { tableLastRowVisibleComponentState } from '@/object-record/record-table/states/tableLastRowVisibleComponentState'; import { tableLastRowVisibleComponentState } from '@/object-record/record-table/states/tableLastRowVisibleComponentState';
@ -18,7 +18,7 @@ import { isNonEmptyString, isNull } from '@sniptt/guards';
import { useScrollToPosition } from '~/hooks/useScrollToPosition'; import { useScrollToPosition } from '~/hooks/useScrollToPosition';
export const RecordTableNoRecordGroupBodyEffect = () => { export const RecordTableNoRecordGroupBodyEffect = () => {
const { objectNameSingular } = useContext(RecordTableContext); const { objectNameSingular } = useRecordTableContextOrThrow();
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);

View File

@ -1,4 +1,4 @@
import { useContext, useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil'; import { useRecoilState, useRecoilValue } from 'recoil';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState'; import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
@ -7,13 +7,13 @@ import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useC
import { useLazyLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLazyLoadRecordIndexTable'; import { useLazyLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLazyLoadRecordIndexTable';
import { recordIndexHasFetchedAllRecordsByGroupComponentState } from '@/object-record/record-index/states/recordIndexHasFetchedAllRecordsByGroupComponentState'; import { recordIndexHasFetchedAllRecordsByGroupComponentState } from '@/object-record/record-index/states/recordIndexHasFetchedAllRecordsByGroupComponentState';
import { ROW_HEIGHT } from '@/object-record/record-table/constants/RowHeight'; import { ROW_HEIGHT } from '@/object-record/record-table/constants/RowHeight';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useSetRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentFamilyStateV2'; import { useSetRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentFamilyStateV2';
import { isNonEmptyString, isNull } from '@sniptt/guards'; import { isNonEmptyString, isNull } from '@sniptt/guards';
import { useScrollToPosition } from '~/hooks/useScrollToPosition'; import { useScrollToPosition } from '~/hooks/useScrollToPosition';
export const RecordTableRecordGroupBodyEffect = () => { export const RecordTableRecordGroupBodyEffect = () => {
const { objectNameSingular } = useContext(RecordTableContext); const { objectNameSingular } = useRecordTableContextOrThrow();
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);

View File

@ -1,13 +1,13 @@
import { RecordGroupContext } from '@/object-record/record-group/states/context/RecordGroupContext'; import { RecordGroupContext } from '@/object-record/record-group/states/context/RecordGroupContext';
import { visibleRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentSelector'; import { visibleRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentSelector';
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector'; import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
import { RecordTableRecordGroupBodyContextProvider } from '@/object-record/record-table/components/RecordTableRecordGroupBodyContextProvider';
import { RecordTableRecordGroupRows } from '@/object-record/record-table/components/RecordTableRecordGroupRows'; import { RecordTableRecordGroupRows } from '@/object-record/record-table/components/RecordTableRecordGroupRows';
import { RecordTableBodyDroppable } from '@/object-record/record-table/record-table-body/components/RecordTableBodyDroppable'; import { RecordTableBodyDroppable } from '@/object-record/record-table/record-table-body/components/RecordTableBodyDroppable';
import { RecordTableBodyLoading } from '@/object-record/record-table/record-table-body/components/RecordTableBodyLoading'; import { RecordTableBodyLoading } from '@/object-record/record-table/record-table-body/components/RecordTableBodyLoading';
import { RecordTableBodyRecordGroupDragDropContextProvider } from '@/object-record/record-table/record-table-body/components/RecordTableBodyRecordGroupDragDropContextProvider'; import { RecordTableBodyRecordGroupDragDropContextProvider } from '@/object-record/record-table/record-table-body/components/RecordTableBodyRecordGroupDragDropContextProvider';
import { RecordTablePendingRow } from '@/object-record/record-table/record-table-row/components/RecordTablePendingRow'; import { RecordTableRecordGroupEmptyRow } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupEmptyRow';
import { RecordTableRecordGroupSection } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupSection'; import { RecordTableRecordGroupSection } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupSection';
import { RecordTableRecordGroupSectionLoadMore } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupSectionLoadMore';
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState'; import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
@ -30,20 +30,19 @@ export const RecordTableRecordGroupsBody = () => {
return ( return (
<RecordTableBodyRecordGroupDragDropContextProvider> <RecordTableBodyRecordGroupDragDropContextProvider>
<RecordTableBodyDroppable isDropDisabled> {visibleRecordGroupIds.map((recordGroupId, index) => (
<RecordTablePendingRow /> <RecordTableRecordGroupBodyContextProvider
</RecordTableBodyDroppable>
{visibleRecordGroupIds.map((recordGroupId) => (
<RecordGroupContext.Provider
key={recordGroupId} key={recordGroupId}
value={{ recordGroupId }} recordGroupId={recordGroupId}
> >
<RecordTableBodyDroppable recordGroupId={recordGroupId}> {index > 0 && <RecordTableRecordGroupEmptyRow />}
<RecordTableRecordGroupSection /> <RecordGroupContext.Provider value={{ recordGroupId }}>
<RecordTableRecordGroupRows /> <RecordTableBodyDroppable recordGroupId={recordGroupId}>
<RecordTableRecordGroupSectionLoadMore /> <RecordTableRecordGroupSection />
</RecordTableBodyDroppable> <RecordTableRecordGroupRows />
</RecordGroupContext.Provider> </RecordTableBodyDroppable>
</RecordGroupContext.Provider>
</RecordTableRecordGroupBodyContextProvider>
))} ))}
</RecordTableBodyRecordGroupDragDropContextProvider> </RecordTableBodyRecordGroupDragDropContextProvider>
); );

View File

@ -5,8 +5,8 @@ import { BORDER_COMMON, ThemeContext } from 'twenty-ui';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { useFieldFocus } from '@/object-record/record-field/hooks/useFieldFocus'; import { useFieldFocus } from '@/object-record/record-field/hooks/useFieldFocus';
import { CellHotkeyScopeContext } from '@/object-record/record-table/contexts/CellHotkeyScopeContext'; import { CellHotkeyScopeContext } from '@/object-record/record-table/contexts/CellHotkeyScopeContext';
import { useRecordTableBodyContextOrThrow } from '@/object-record/record-table/contexts/RecordTableBodyContext';
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext'; import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
import { import {
DEFAULT_CELL_SCOPE, DEFAULT_CELL_SCOPE,
useOpenRecordTableCellFromCell, useOpenRecordTableCellFromCell,
@ -47,7 +47,7 @@ export const RecordTableCellBaseContainer = ({
const { hasSoftFocus, cellPosition } = useContext(RecordTableCellContext); const { hasSoftFocus, cellPosition } = useContext(RecordTableCellContext);
const { onMoveSoftFocusToCell, onCellMouseEnter } = const { onMoveSoftFocusToCell, onCellMouseEnter } =
useContext(RecordTableContext); useRecordTableBodyContextOrThrow();
const handleContainerMouseMove = () => { const handleContainerMouseMove = () => {
setIsFocused(true); setIsFocused(true);

View File

@ -1,5 +1,5 @@
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableBodyContextOrThrow } from '@/object-record/record-table/contexts/RecordTableBodyContext';
import { useContext } from 'react'; import { useContext } from 'react';
import { RecordTableCellDisplayContainer } from './RecordTableCellDisplayContainer'; import { RecordTableCellDisplayContainer } from './RecordTableCellDisplayContainer';
@ -7,9 +7,10 @@ export const RecordTableCellDisplayMode = ({
children, children,
softFocus, softFocus,
}: React.PropsWithChildren<{ softFocus?: boolean }>) => { }: React.PropsWithChildren<{ softFocus?: boolean }>) => {
const { onActionMenuDropdownOpened } = useContext(RecordTableContext);
const { recordId } = useContext(FieldContext); const { recordId } = useContext(FieldContext);
const { onActionMenuDropdownOpened } = useRecordTableBodyContextOrThrow();
const handleActionMenuDropdown = (event: React.MouseEvent) => { const handleActionMenuDropdown = (event: React.MouseEvent) => {
onActionMenuDropdownOpened(event, recordId); onActionMenuDropdownOpened(event, recordId);
}; };

View File

@ -6,7 +6,7 @@ import { isFieldRelation } from '@/object-record/record-field/types/guards/isFie
import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect'; import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect';
import { RecordUpdateContext } from '@/object-record/record-table/contexts/EntityUpdateMutationHookContext'; import { RecordUpdateContext } from '@/object-record/record-table/contexts/EntityUpdateMutationHookContext';
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext'; import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext'; import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope'; import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
@ -18,8 +18,10 @@ export const RecordTableCellFieldContextWrapper = ({
}: { }: {
children: ReactNode; children: ReactNode;
}) => { }) => {
const { objectMetadataItem } = useContext(RecordTableContext); const { objectMetadataItem } = useRecordTableContextOrThrow();
const { columnDefinition } = useContext(RecordTableCellContext); const { columnDefinition } = useContext(RecordTableCellContext);
const { recordId, pathToShowPage } = useContext(RecordTableRowContext); const { recordId, pathToShowPage } = useContext(RecordTableRowContext);
const updateRecord = useContext(RecordUpdateContext); const updateRecord = useContext(RecordUpdateContext);

View File

@ -4,17 +4,18 @@ import { FieldInput } from '@/object-record/record-field/components/FieldInput';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly'; import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent'; import { FieldInputEvent } from '@/object-record/record-field/types/FieldInputEvent';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableBodyContextOrThrow } from '@/object-record/record-table/contexts/RecordTableBodyContext';
import { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropdownFocusIdForRecordField'; import { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropdownFocusIdForRecordField';
import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId'; import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId';
import { activeDropdownFocusIdState } from '@/ui/layout/dropdown/states/activeDropdownFocusIdState'; import { activeDropdownFocusIdState } from '@/ui/layout/dropdown/states/activeDropdownFocusIdState';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
export const RecordTableCellFieldInput = () => { export const RecordTableCellFieldInput = () => {
const { onUpsertRecord, onMoveFocus, onCloseTableCell } =
useContext(RecordTableContext);
const { recordId, fieldDefinition } = useContext(FieldContext); const { recordId, fieldDefinition } = useContext(FieldContext);
const { onUpsertRecord, onMoveFocus, onCloseTableCell } =
useRecordTableBodyContextOrThrow();
const isFieldReadOnly = useIsFieldValueReadOnly(); const isFieldReadOnly = useIsFieldValueReadOnly();
const handleEnter: FieldInputEvent = (persistField) => { const handleEnter: FieldInputEvent = (persistField) => {

View File

@ -3,19 +3,25 @@ import { useContext } from 'react';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext'; import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd'; import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
import { css } from '@emotion/react';
import { IconListViewGrip } from 'twenty-ui'; import { IconListViewGrip } from 'twenty-ui';
const StyledContainer = styled.div` const StyledContainer = styled.div<{ isPendingRow?: boolean }>`
cursor: grab;
width: 16px;
height: 32px;
z-index: 200;
display: flex;
&:hover .icon {
opacity: 1;
}
border-color: transparent; border-color: transparent;
cursor: grab;
display: flex;
height: 32px;
width: 16px;
${({ isPendingRow }) =>
!isPendingRow
? css`
&:hover .icon {
opacity: 1;
}
`
: ''};
z-index: 200;
`; `;
const StyledIconWrapper = styled.div<{ isDragging: boolean }>` const StyledIconWrapper = styled.div<{ isDragging: boolean }>`
@ -24,7 +30,9 @@ const StyledIconWrapper = styled.div<{ isDragging: boolean }>`
`; `;
export const RecordTableCellGrip = () => { export const RecordTableCellGrip = () => {
const { dragHandleProps, isDragging } = useContext(RecordTableRowContext); const { dragHandleProps, isDragging, isPendingRow } = useContext(
RecordTableRowContext,
);
return ( return (
<RecordTableTd <RecordTableTd
@ -34,7 +42,7 @@ export const RecordTableCellGrip = () => {
hasRightBorder={false} hasRightBorder={false}
hasBottomBorder={false} hasBottomBorder={false}
> >
<StyledContainer> <StyledContainer isPendingRow={isPendingRow}>
<StyledIconWrapper className="icon" isDragging={isDragging}> <StyledIconWrapper className="icon" isDragging={isDragging}>
<IconListViewGrip /> <IconListViewGrip />
</StyledIconWrapper> </StyledIconWrapper>

View File

@ -21,7 +21,7 @@ import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly'; import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableBodyContextOrThrow } from '@/object-record/record-table/contexts/RecordTableBodyContext';
import { RecordTableCellDisplayContainer } from './RecordTableCellDisplayContainer'; import { RecordTableCellDisplayContainer } from './RecordTableCellDisplayContainer';
type RecordTableCellSoftFocusModeProps = { type RecordTableCellSoftFocusModeProps = {
@ -36,6 +36,8 @@ export const RecordTableCellSoftFocusMode = ({
const { columnIndex } = useContext(RecordTableCellContext); const { columnIndex } = useContext(RecordTableCellContext);
const { recordId } = useContext(FieldContext); const { recordId } = useContext(FieldContext);
const { onActionMenuDropdownOpened } = useRecordTableBodyContextOrThrow();
const isFieldReadOnly = useIsFieldValueReadOnly(); const isFieldReadOnly = useIsFieldValueReadOnly();
const { openTableCell } = useOpenRecordTableCellFromCell(); const { openTableCell } = useOpenRecordTableCellFromCell();
@ -135,8 +137,6 @@ export const RecordTableCellSoftFocusMode = ({
*/ */
}; };
const { onActionMenuDropdownOpened } = useContext(RecordTableContext);
const handleActionMenuDropdown = (event: React.MouseEvent) => { const handleActionMenuDropdown = (event: React.MouseEvent) => {
onActionMenuDropdownOpened(event, recordId); onActionMenuDropdownOpened(event, recordId);
}; };

View File

@ -0,0 +1,101 @@
import { act, renderHook } from '@testing-library/react';
import { RecoilRoot } from 'recoil';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { textfieldDefinition } from '@/object-record/record-field/__mocks__/fieldDefinitions';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider';
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
import {
recordTableCell,
recordTableRow,
} from '@/object-record/record-table/record-table-cell/hooks/__mocks__/cell';
import { useCloseRecordTableCellInGroup } from '@/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellInGroup';
import { currentTableCellInEditModePositionComponentState } from '@/object-record/record-table/states/currentTableCellInEditModePositionComponentState';
import { isTableCellInEditModeComponentFamilyState } from '@/object-record/record-table/states/isTableCellInEditModeComponentFamilyState';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
const setHotkeyScope = jest.fn();
jest.mock('@/ui/utilities/hotkey/hooks/useSetHotkeyScope', () => ({
useSetHotkeyScope: () => setHotkeyScope,
}));
const onColumnsChange = jest.fn();
const recordTableId = 'scopeId';
const recordGroupId = 'recordGroupId';
const Wrapper = ({ children }: { children: React.ReactNode }) => (
<RecoilRoot
initializeState={(snapshot) => {
snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems);
}}
>
<RecordTableComponentInstance
recordTableId={recordTableId}
onColumnsChange={onColumnsChange}
>
<RecordTableContextProvider
recordTableId={recordTableId}
viewBarId="viewBarId"
objectNameSingular={CoreObjectNameSingular.Person}
>
<FieldContext.Provider
value={{
fieldDefinition: textfieldDefinition,
recordId: 'recordId',
hotkeyScope: TableHotkeyScope.Table,
isLabelIdentifier: false,
}}
>
<RecordTableRowContext.Provider value={recordTableRow}>
<RecordTableCellContext.Provider
value={{ ...recordTableCell, columnIndex: 0 }}
>
{children}
</RecordTableCellContext.Provider>
</RecordTableRowContext.Provider>
</FieldContext.Provider>
</RecordTableContextProvider>
</RecordTableComponentInstance>
</RecoilRoot>
);
describe('useCloseRecordTableCellInGroup', () => {
it('should work as expected', async () => {
const { result } = renderHook(
() => {
const currentTableCellInEditModePosition = useRecoilComponentValueV2(
currentTableCellInEditModePositionComponentState,
);
const isTableCellInEditMode = useRecoilComponentFamilyValueV2(
isTableCellInEditModeComponentFamilyState,
currentTableCellInEditModePosition,
);
return {
...useCloseRecordTableCellInGroup(recordGroupId),
...useDragSelect(),
isTableCellInEditMode,
};
},
{
wrapper: Wrapper,
},
);
act(() => {
result.current.closeTableCellInGroup();
});
expect(result.current.isDragSelectionStartEnabled()).toBe(true);
expect(result.current.isTableCellInEditMode).toBe(false);
expect(setHotkeyScope).toHaveBeenCalledWith('table-soft-focus');
});
});

View File

@ -1,22 +1,26 @@
import { act, renderHook } from '@testing-library/react'; import { act, renderHook } from '@testing-library/react';
import { RecoilRoot } from 'recoil'; import { RecoilRoot } from 'recoil';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { textfieldDefinition } from '@/object-record/record-field/__mocks__/fieldDefinitions'; import { textfieldDefinition } from '@/object-record/record-field/__mocks__/fieldDefinitions';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext'; import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance'; import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider';
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext'; import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext'; import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { import {
recordTableCell, recordTableCell,
recordTableRow, recordTableRow,
} from '@/object-record/record-table/record-table-cell/hooks/__mocks__/cell'; } from '@/object-record/record-table/record-table-cell/hooks/__mocks__/cell';
import { useCloseRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useCloseRecordTableCell'; import { useCloseRecordTableCellNoGroup } from '@/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellNoGroup';
import { currentTableCellInEditModePositionComponentState } from '@/object-record/record-table/states/currentTableCellInEditModePositionComponentState'; import { currentTableCellInEditModePositionComponentState } from '@/object-record/record-table/states/currentTableCellInEditModePositionComponentState';
import { isTableCellInEditModeComponentFamilyState } from '@/object-record/record-table/states/isTableCellInEditModeComponentFamilyState'; import { isTableCellInEditModeComponentFamilyState } from '@/object-record/record-table/states/isTableCellInEditModeComponentFamilyState';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect'; import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2'; import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
const setHotkeyScope = jest.fn(); const setHotkeyScope = jest.fn();
@ -28,32 +32,42 @@ const onColumnsChange = jest.fn();
const recordTableId = 'scopeId'; const recordTableId = 'scopeId';
const Wrapper = ({ children }: { children: React.ReactNode }) => ( const Wrapper = ({ children }: { children: React.ReactNode }) => (
<RecoilRoot> <RecoilRoot
initializeState={(snapshot) => {
snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems);
}}
>
<RecordTableComponentInstance <RecordTableComponentInstance
recordTableId={recordTableId} recordTableId={recordTableId}
onColumnsChange={onColumnsChange} onColumnsChange={onColumnsChange}
> >
<FieldContext.Provider <RecordTableContextProvider
value={{ recordTableId={recordTableId}
fieldDefinition: textfieldDefinition, viewBarId="viewBarId"
recordId: 'recordId', objectNameSingular={CoreObjectNameSingular.Person}
hotkeyScope: TableHotkeyScope.Table,
isLabelIdentifier: false,
}}
> >
<RecordTableRowContext.Provider value={recordTableRow}> <FieldContext.Provider
<RecordTableCellContext.Provider value={{
value={{ ...recordTableCell, columnIndex: 0 }} fieldDefinition: textfieldDefinition,
> recordId: 'recordId',
{children} hotkeyScope: TableHotkeyScope.Table,
</RecordTableCellContext.Provider> isLabelIdentifier: false,
</RecordTableRowContext.Provider> }}
</FieldContext.Provider> >
<RecordTableRowContext.Provider value={recordTableRow}>
<RecordTableCellContext.Provider
value={{ ...recordTableCell, columnIndex: 0 }}
>
{children}
</RecordTableCellContext.Provider>
</RecordTableRowContext.Provider>
</FieldContext.Provider>
</RecordTableContextProvider>
</RecordTableComponentInstance> </RecordTableComponentInstance>
</RecoilRoot> </RecoilRoot>
); );
describe('useCloseRecordTableCell', () => { describe('useCloseRecordTableCellNoGroup', () => {
it('should work as expected', async () => { it('should work as expected', async () => {
const { result } = renderHook( const { result } = renderHook(
() => { () => {
@ -65,7 +79,7 @@ describe('useCloseRecordTableCell', () => {
currentTableCellInEditModePosition, currentTableCellInEditModePosition,
); );
return { return {
...useCloseRecordTableCell(), ...useCloseRecordTableCellNoGroup(),
...useDragSelect(), ...useDragSelect(),
isTableCellInEditMode, isTableCellInEditMode,
}; };
@ -76,7 +90,7 @@ describe('useCloseRecordTableCell', () => {
); );
act(() => { act(() => {
result.current.closeTableCell(); result.current.closeTableCellNoGroup();
}); });
expect(result.current.isDragSelectionStartEnabled()).toBe(true); expect(result.current.isDragSelectionStartEnabled()).toBe(true);

View File

@ -0,0 +1,56 @@
import { useRecoilCallback } from 'recoil';
import { SOFT_FOCUS_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/SoftFocusClickOutsideListenerId';
import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useCloseCurrentTableCellInEditMode } from '@/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode';
import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
export const useCloseRecordTableCellInGroup = (recordGroupId: string) => {
const { recordTableId } = useRecordTableContextOrThrow();
const setHotkeyScope = useSetHotkeyScope();
const { setDragSelectionStartEnabled } = useDragSelect();
const { toggleClickOutsideListener } = useClickOutsideListener(
SOFT_FOCUS_CLICK_OUTSIDE_LISTENER_ID,
);
const closeCurrentTableCellInEditMode =
useCloseCurrentTableCellInEditMode(recordTableId);
const recordTablePendingRecordIdByGroupFamilyState =
useRecoilComponentCallbackStateV2(
recordTablePendingRecordIdByGroupComponentFamilyState,
recordTableId,
);
const closeTableCellInGroup = useRecoilCallback(
({ reset }) =>
() => {
toggleClickOutsideListener(true);
setDragSelectionStartEnabled(true);
closeCurrentTableCellInEditMode();
setHotkeyScope(TableHotkeyScope.TableSoftFocus);
reset(recordTablePendingRecordIdByGroupFamilyState(recordGroupId));
},
[
closeCurrentTableCellInEditMode,
recordGroupId,
recordTablePendingRecordIdByGroupFamilyState,
setDragSelectionStartEnabled,
setHotkeyScope,
toggleClickOutsideListener,
],
);
return {
closeTableCellInGroup,
};
};

View File

@ -5,13 +5,18 @@ import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener'; import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useCloseCurrentTableCellInEditMode } from '@/object-record/record-table/hooks/internal/useCloseCurrentTableCellInEditMode';
import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState'; import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { useCloseCurrentTableCellInEditMode } from '../../hooks/internal/useCloseCurrentTableCellInEditMode'; import { useCallback } from 'react';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
export const useCloseRecordTableCellNoGroup = () => {
const { recordTableId } = useRecordTableContextOrThrow();
export const useCloseRecordTableCellV2 = (recordTableId: string) => {
const setHotkeyScope = useSetHotkeyScope(); const setHotkeyScope = useSetHotkeyScope();
const { setDragSelectionStartEnabled } = useDragSelect(); const { setDragSelectionStartEnabled } = useDragSelect();
const { toggleClickOutsideListener } = useClickOutsideListener( const { toggleClickOutsideListener } = useClickOutsideListener(
@ -25,18 +30,25 @@ export const useCloseRecordTableCellV2 = (recordTableId: string) => {
recordTablePendingRecordIdComponentState, recordTablePendingRecordIdComponentState,
recordTableId, recordTableId,
); );
const resetRecordTablePendingRecordId = const resetRecordTablePendingRecordId =
useResetRecoilState(pendingRecordIdState); useResetRecoilState(pendingRecordIdState);
const closeTableCell = async () => { const closeTableCellNoGroup = useCallback(() => {
toggleClickOutsideListener(true); toggleClickOutsideListener(true);
setDragSelectionStartEnabled(true); setDragSelectionStartEnabled(true);
closeCurrentTableCellInEditMode(); closeCurrentTableCellInEditMode();
setHotkeyScope(TableHotkeyScope.TableSoftFocus); setHotkeyScope(TableHotkeyScope.TableSoftFocus);
resetRecordTablePendingRecordId(); resetRecordTablePendingRecordId();
}; }, [
closeCurrentTableCellInEditMode,
resetRecordTablePendingRecordId,
setDragSelectionStartEnabled,
setHotkeyScope,
toggleClickOutsideListener,
]);
return { return {
closeTableCell, closeTableCellNoGroup,
}; };
}; };

View File

@ -1,38 +0,0 @@
import { SOFT_FOCUS_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/SoftFocusClickOutsideListenerId';
import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { useResetRecoilState } from 'recoil';
import { useCloseCurrentTableCellInEditMode } from '../../hooks/internal/useCloseCurrentTableCellInEditMode';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
export const useCloseRecordTableCell = () => {
const setHotkeyScope = useSetHotkeyScope();
const { setDragSelectionStartEnabled } = useDragSelect();
const { toggleClickOutsideListener } = useClickOutsideListener(
SOFT_FOCUS_CLICK_OUTSIDE_LISTENER_ID,
);
const closeCurrentTableCellInEditMode = useCloseCurrentTableCellInEditMode();
const pendingRecordIdState = useRecoilComponentCallbackStateV2(
recordTablePendingRecordIdComponentState,
);
const resetRecordTablePendingRecordId =
useResetRecoilState(pendingRecordIdState);
const closeTableCell = async () => {
toggleClickOutsideListener(true);
setDragSelectionStartEnabled(true);
closeCurrentTableCellInEditMode();
setHotkeyScope(TableHotkeyScope.TableSoftFocus);
resetRecordTablePendingRecordId();
};
return {
closeTableCell,
};
};

View File

@ -5,12 +5,12 @@ import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useI
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition'; import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { CellHotkeyScopeContext } from '@/object-record/record-table/contexts/CellHotkeyScopeContext'; import { CellHotkeyScopeContext } from '@/object-record/record-table/contexts/CellHotkeyScopeContext';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext'; import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { useCurrentTableCellPosition } from '@/object-record/record-table/record-table-cell/hooks/useCurrentCellPosition'; import { useCurrentTableCellPosition } from '@/object-record/record-table/record-table-cell/hooks/useCurrentCellPosition';
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition'; import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useRecordTableBodyContextOrThrow } from '@/object-record/record-table/contexts/RecordTableBodyContext';
import { TableHotkeyScope } from '../../types/TableHotkeyScope'; import { TableHotkeyScope } from '../../types/TableHotkeyScope';
export const DEFAULT_CELL_SCOPE: HotkeyScope = { export const DEFAULT_CELL_SCOPE: HotkeyScope = {
@ -28,13 +28,16 @@ export type OpenTableCellArgs = {
}; };
export const useOpenRecordTableCellFromCell = () => { export const useOpenRecordTableCellFromCell = () => {
const { onOpenTableCell } = useContext(RecordTableContext);
const cellPosition = useCurrentTableCellPosition();
const customCellHotkeyScope = useContext(CellHotkeyScopeContext); const customCellHotkeyScope = useContext(CellHotkeyScopeContext);
const { recordId, fieldDefinition } = useContext(FieldContext); const { recordId, fieldDefinition } = useContext(FieldContext);
const { pathToShowPage, objectNameSingular } = useContext( const { pathToShowPage, objectNameSingular } = useContext(
RecordTableRowContext, RecordTableRowContext,
); );
const { onOpenTableCell } = useRecordTableBodyContextOrThrow();
const cellPosition = useCurrentTableCellPosition();
const isFieldReadOnly = useIsFieldValueReadOnly(); const isFieldReadOnly = useIsFieldValueReadOnly();
const openTableCell = ( const openTableCell = (

View File

@ -20,12 +20,11 @@ import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useC
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { isDefined } from '~/utils/isDefined'; import { isDefined } from '~/utils/isDefined';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId'; import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId';
import { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropdownFocusIdForRecordField'; import { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropdownFocusIdForRecordField';
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious'; import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
import { useClickOustideListenerStates } from '@/ui/utilities/pointer-event/hooks/useClickOustideListenerStates'; import { useClickOustideListenerStates } from '@/ui/utilities/pointer-event/hooks/useClickOustideListenerStates';
import { useContext } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { TableHotkeyScope } from '../../types/TableHotkeyScope'; import { TableHotkeyScope } from '../../types/TableHotkeyScope';
@ -49,7 +48,7 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
const { getClickOutsideListenerIsActivatedState } = const { getClickOutsideListenerIsActivatedState } =
useClickOustideListenerStates(RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID); useClickOustideListenerStates(RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID);
const { indexIdentifierUrl } = useContext(RecordIndexRootPropsContext); const { indexIdentifierUrl } = useRecordIndexContextOrThrow();
const moveEditModeToTableCellPosition = const moveEditModeToTableCellPosition =
useMoveEditModeToTableCellPosition(tableScopeId); useMoveEditModeToTableCellPosition(tableScopeId);

View File

@ -1,12 +1,11 @@
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { MOBILE_VIEWPORT } from 'twenty-ui'; import { MOBILE_VIEWPORT } from 'twenty-ui';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableHeaderCell } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderCell'; import { RecordTableHeaderCell } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderCell';
import { RecordTableHeaderCheckboxColumn } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderCheckboxColumn'; import { RecordTableHeaderCheckboxColumn } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderCheckboxColumn';
import { RecordTableHeaderDragDropColumn } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderDragDropColumn'; import { RecordTableHeaderDragDropColumn } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderDragDropColumn';
import { RecordTableHeaderLastColumn } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderLastColumn'; import { RecordTableHeaderLastColumn } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderLastColumn';
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
const StyledTableHead = styled.thead` const StyledTableHead = styled.thead`
cursor: pointer; cursor: pointer;
@ -77,14 +76,8 @@ const StyledTableHead = styled.thead`
} }
`; `;
export const RecordTableHeader = ({ export const RecordTableHeader = () => {
objectNameSingular, const { visibleTableColumns } = useRecordTableContextOrThrow();
}: {
objectNameSingular: string;
}) => {
const visibleTableColumns = useRecoilComponentValueV2(
visibleTableColumnsComponentSelector,
);
return ( return (
<StyledTableHead id="record-table-header" data-select-disable> <StyledTableHead id="record-table-header" data-select-disable>
@ -92,11 +85,7 @@ export const RecordTableHeader = ({
<RecordTableHeaderDragDropColumn /> <RecordTableHeaderDragDropColumn />
<RecordTableHeaderCheckboxColumn /> <RecordTableHeaderCheckboxColumn />
{visibleTableColumns.map((column) => ( {visibleTableColumns.map((column) => (
<RecordTableHeaderCell <RecordTableHeaderCell key={column.fieldMetadataId} column={column} />
key={column.fieldMetadataId}
column={column}
objectNameSingular={objectNameSingular}
/>
))} ))}
<RecordTableHeaderLastColumn /> <RecordTableHeaderLastColumn />
</tr> </tr>

View File

@ -3,9 +3,9 @@ import { useCallback, useMemo, useState } from 'react';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { IconPlus, LightIconButton } from 'twenty-ui'; import { IconPlus, LightIconButton } from 'twenty-ui';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { isObjectMetadataReadOnly } from '@/object-metadata/utils/isObjectMetadataReadOnly'; import { isObjectMetadataReadOnly } from '@/object-metadata/utils/isObjectMetadataReadOnly';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords'; import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords';
import { useTableColumns } from '@/object-record/record-table/hooks/useTableColumns'; import { useTableColumns } from '@/object-record/record-table/hooks/useTableColumns';
import { RecordTableColumnHeadWithDropdown } from '@/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown'; import { RecordTableColumnHeadWithDropdown } from '@/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown';
@ -95,16 +95,14 @@ const StyledHeaderIcon = styled.div`
margin: ${({ theme }) => theme.spacing(1, 1, 1, 1.5)}; margin: ${({ theme }) => theme.spacing(1, 1, 1, 1.5)};
`; `;
type RecordTableHeaderCellProps = {
column: ColumnDefinition<FieldMetadata>;
};
export const RecordTableHeaderCell = ({ export const RecordTableHeaderCell = ({
column, column,
objectNameSingular, }: RecordTableHeaderCellProps) => {
}: { const { recordTableId, objectMetadataItem } = useRecordTableContextOrThrow();
column: ColumnDefinition<FieldMetadata>;
objectNameSingular: string;
}) => {
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const resizeFieldOffsetState = useRecoilComponentCallbackStateV2( const resizeFieldOffsetState = useRecoilComponentCallbackStateV2(
resizeFieldOffsetComponentState, resizeFieldOffsetComponentState,
@ -199,7 +197,7 @@ export const RecordTableHeaderCell = ({
const disableColumnResize = const disableColumnResize =
column.isLabelIdentifier && isMobile && !isRecordTableScrolledLeft; column.isLabelIdentifier && isMobile && !isRecordTableScrolledLeft;
const { createNewTableRecord } = useCreateNewTableRecord(); const { createNewTableRecord } = useCreateNewTableRecord(recordTableId);
const handlePlusButtonClick = () => { const handlePlusButtonClick = () => {
createNewTableRecord(); createNewTableRecord();

View File

@ -1,11 +1,11 @@
import { useCallback, useContext } from 'react'; import { useCallback } from 'react';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { useSetRecoilState } from 'recoil'; import { useSetRecoilState } from 'recoil';
import { IconSettings, MenuItem, UndecoratedLink, useIcons } from 'twenty-ui'; import { IconSettings, MenuItem, UndecoratedLink, useIcons } from 'twenty-ui';
import { getObjectSlug } from '@/object-metadata/utils/getObjectSlug'; import { getObjectSlug } from '@/object-metadata/utils/getObjectSlug';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useTableColumns } from '@/object-record/record-table/hooks/useTableColumns'; import { useTableColumns } from '@/object-record/record-table/hooks/useTableColumns';
import { hiddenTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/hiddenTableColumnsComponentSelector'; import { hiddenTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/hiddenTableColumnsComponentSelector';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition'; import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
@ -16,7 +16,8 @@ import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMe
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
export const RecordTableHeaderPlusButtonContent = () => { export const RecordTableHeaderPlusButtonContent = () => {
const { objectMetadataItem } = useContext(RecordTableContext); const { objectMetadataItem } = useRecordTableContextOrThrow();
const { closeDropdown } = useDropdown(); const { closeDropdown } = useDropdown();
const hiddenTableColumns = useRecoilComponentValueV2( const hiddenTableColumns = useRecoilComponentValueV2(

View File

@ -0,0 +1,25 @@
import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
import { RecordTableRow } from '@/object-record/record-table/record-table-row/components/RecordTableRow';
import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
export const RecordTablePendingRecordGroupRow = () => {
const currentRecordGroupId = useCurrentRecordGroupId();
const pendingRecordId = useRecoilComponentFamilyValueV2(
recordTablePendingRecordIdByGroupComponentFamilyState,
currentRecordGroupId,
);
if (!pendingRecordId) return <></>;
return (
<RecordTableRow
key={pendingRecordId}
recordId={pendingRecordId}
rowIndexForDrag={-1}
rowIndexForFocus={-1}
isPendingRow
/>
);
};

View File

@ -4,8 +4,8 @@ import { ReactNode, useContext, useEffect, useRef } from 'react';
import { useInView } from 'react-intersection-observer'; import { useInView } from 'react-intersection-observer';
import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage'; import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext'; import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { RecordTableTr } from '@/object-record/record-table/record-table-row/components/RecordTableTr'; import { RecordTableTr } from '@/object-record/record-table/record-table-row/components/RecordTableTr';
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState'; import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
@ -31,8 +31,8 @@ export const RecordTableRowWrapper = ({
}: RecordTableRowWrapperProps) => { }: RecordTableRowWrapperProps) => {
const trRef = useRef<HTMLTableRowElement>(null); const trRef = useRef<HTMLTableRowElement>(null);
const { objectMetadataItem } = useContext(RecordTableContext); const { objectMetadataItem } = useRecordTableContextOrThrow();
const { onIndexRecordsLoaded } = useContext(RecordIndexRootPropsContext); const { onIndexRecordsLoaded } = useRecordIndexContextOrThrow();
const theme = useTheme(); const theme = useTheme();
@ -78,7 +78,12 @@ export const RecordTableRowWrapper = ({
}, [inView, onIndexRecordsLoaded]); }, [inView, onIndexRecordsLoaded]);
return ( return (
<Draggable key={recordId} draggableId={recordId} index={rowIndexForDrag}> <Draggable
key={recordId}
draggableId={recordId}
index={rowIndexForDrag}
isDragDisabled={isPendingRow}
>
{(draggableProvided, draggableSnapshot) => ( {(draggableProvided, draggableSnapshot) => (
<RecordTableTr <RecordTableTr
ref={(node) => { ref={(node) => {

View File

@ -0,0 +1,7 @@
import styled from '@emotion/styled';
const StyledTrContainer = styled.tr`
height: 32px;
`;
export const RecordTableRecordGroupEmptyRow = StyledTrContainer;

View File

@ -0,0 +1,36 @@
import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords';
import { RecordTableActionRow } from '@/object-record/record-table/record-table-row/components/RecordTableActionRow';
import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { IconPlus } from 'twenty-ui';
import { isDefined } from '~/utils/isDefined';
export const RecordTableRecordGroupSectionAddNew = () => {
const { recordTableId } = useRecordTableContextOrThrow();
const currentRecordGroupId = useCurrentRecordGroupId();
const pendingRecordId = useRecoilComponentFamilyValueV2(
recordTablePendingRecordIdByGroupComponentFamilyState,
currentRecordGroupId,
);
const { createNewTableRecordInGroup } =
useCreateNewTableRecord(recordTableId);
const handleAddNewRecord = () => {
createNewTableRecordInGroup(currentRecordGroupId);
};
if (isDefined(pendingRecordId)) return null;
return (
<RecordTableActionRow
LeftIcon={IconPlus}
text="Add new"
onClick={handleAddNewRecord}
/>
);
};

View File

@ -1,14 +1,13 @@
import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId'; import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
import { useLazyLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLazyLoadRecordIndexTable'; import { useLazyLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLazyLoadRecordIndexTable';
import { recordIndexHasFetchedAllRecordsByGroupComponentState } from '@/object-record/record-index/states/recordIndexHasFetchedAllRecordsByGroupComponentState'; import { recordIndexHasFetchedAllRecordsByGroupComponentState } from '@/object-record/record-index/states/recordIndexHasFetchedAllRecordsByGroupComponentState';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableActionRow } from '@/object-record/record-table/record-table-row/components/RecordTableActionRow'; import { RecordTableActionRow } from '@/object-record/record-table/record-table-row/components/RecordTableActionRow';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2'; import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { useContext } from 'react';
import { IconArrowDown } from 'twenty-ui'; import { IconArrowDown } from 'twenty-ui';
export const RecordTableRecordGroupSectionLoadMore = () => { export const RecordTableRecordGroupSectionLoadMore = () => {
const { objectNameSingular } = useContext(RecordTableContext); const { objectNameSingular } = useRecordTableContextOrThrow();
const currentRecordGroupId = useCurrentRecordGroupId(); const currentRecordGroupId = useCurrentRecordGroupId();

View File

@ -0,0 +1,10 @@
import { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
import { createComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentFamilyStateV2';
export const recordTablePendingRecordIdByGroupComponentFamilyState =
createComponentFamilyStateV2<string | null, RecordGroupDefinition['id']>({
key: 'recordTablePendingRecordIdByGroupComponentFamilyState',
defaultValue: null,
componentInstanceContext: RecordTableComponentInstanceContext,
});

View File

@ -4,7 +4,7 @@ import { createComponentStateV2 } from '@/ui/utilities/state/component-state/uti
export const recordTablePendingRecordIdComponentState = createComponentStateV2< export const recordTablePendingRecordIdComponentState = createComponentStateV2<
string | null string | null
>({ >({
key: 'recordTablePendingRecordIdState', key: 'recordTablePendingRecordIdComponentState',
defaultValue: null, defaultValue: null,
componentInstanceContext: RecordTableComponentInstanceContext, componentInstanceContext: RecordTableComponentInstanceContext,
}); });

View File

@ -3,7 +3,7 @@ import styled from '@emotion/styled';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext'; import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
import { RecordTableWithWrappers } from '@/object-record/record-table/components/RecordTableWithWrappers'; import { RecordTableWithWrappers } from '@/object-record/record-table/components/RecordTableWithWrappers';
import { SignInBackgroundMockContainerEffect } from '@/sign-in-background-mock/components/SignInBackgroundMockContainerEffect'; import { SignInBackgroundMockContainerEffect } from '@/sign-in-background-mock/components/SignInBackgroundMockContainerEffect';
import { ViewBar } from '@/views/components/ViewBar'; import { ViewBar } from '@/views/components/ViewBar';
@ -28,7 +28,7 @@ export const SignInBackgroundMockContainer = () => {
return ( return (
<StyledContainer> <StyledContainer>
<RecordIndexRootPropsContext.Provider <RecordIndexContextProvider
value={{ value={{
recordIndexId, recordIndexId,
objectNamePlural, objectNamePlural,
@ -36,7 +36,6 @@ export const SignInBackgroundMockContainer = () => {
objectMetadataItem, objectMetadataItem,
onIndexRecordsLoaded: () => {}, onIndexRecordsLoaded: () => {},
indexIdentifierUrl: () => '', indexIdentifierUrl: () => '',
onCreateRecord: () => {},
}} }}
> >
<ViewComponentInstanceContext.Provider <ViewComponentInstanceContext.Provider
@ -69,7 +68,7 @@ export const SignInBackgroundMockContainer = () => {
</ActionMenuComponentInstanceContext.Provider> </ActionMenuComponentInstanceContext.Provider>
</ContextStoreComponentInstanceContext.Provider> </ContextStoreComponentInstanceContext.Provider>
</ViewComponentInstanceContext.Provider> </ViewComponentInstanceContext.Provider>
</RecordIndexRootPropsContext.Provider> </RecordIndexContextProvider>
</StyledContainer> </StyledContainer>
); );
}; };

View File

@ -1,6 +1,6 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { usePersistViewFieldRecords } from '@/views/hooks/internal/usePersistViewFieldRecords'; import { usePersistViewFieldRecords } from '@/views/hooks/internal/usePersistViewFieldRecords';
@ -18,7 +18,6 @@ import { GraphQLView } from '@/views/types/GraphQLView';
import { View } from '@/views/types/View'; import { View } from '@/views/types/View';
import { ViewGroup } from '@/views/types/ViewGroup'; import { ViewGroup } from '@/views/types/ViewGroup';
import { ViewType } from '@/views/types/ViewType'; import { ViewType } from '@/views/types/ViewType';
import { useContext } from 'react';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-ui'; import { isDefined } from 'twenty-ui';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
@ -57,7 +56,7 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
const { createViewFilterGroupRecords } = usePersistViewFilterGroupRecords(); const { createViewFilterGroupRecords } = usePersistViewFilterGroupRecords();
const { objectMetadataItem } = useContext(RecordIndexRootPropsContext); const { objectMetadataItem } = useRecordIndexContextOrThrow();
const createViewFromCurrentView = useRecoilCallback( const createViewFromCurrentView = useRecoilCallback(
({ snapshot, set }) => ({ snapshot, set }) =>

View File

@ -12,9 +12,8 @@ import { RecordIndexContainer } from '@/object-record/record-index/components/Re
import { RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect } from '@/object-record/record-index/components/RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect'; import { RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect } from '@/object-record/record-index/components/RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect';
import { RecordIndexContainerContextStoreObjectMetadataEffect } from '@/object-record/record-index/components/RecordIndexContainerContextStoreObjectMetadataEffect'; import { RecordIndexContainerContextStoreObjectMetadataEffect } from '@/object-record/record-index/components/RecordIndexContainerContextStoreObjectMetadataEffect';
import { RecordIndexPageHeader } from '@/object-record/record-index/components/RecordIndexPageHeader'; import { RecordIndexPageHeader } from '@/object-record/record-index/components/RecordIndexPageHeader';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext'; import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useHandleIndexIdentifierClick } from '@/object-record/record-index/hooks/useHandleIndexIdentifierClick'; import { useHandleIndexIdentifierClick } from '@/object-record/record-index/hooks/useHandleIndexIdentifierClick';
import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords';
import { PageBody } from '@/ui/layout/page/components/PageBody'; import { PageBody } from '@/ui/layout/page/components/PageBody';
import { PageContainer } from '@/ui/layout/page/components/PageContainer'; import { PageContainer } from '@/ui/layout/page/components/PageContainer';
import { PageTitle } from '@/ui/utilities/page-title/components/PageTitle'; import { PageTitle } from '@/ui/utilities/page-title/components/PageTitle';
@ -41,12 +40,6 @@ export const RecordIndexPage = () => {
objectNameSingular, objectNameSingular,
}); });
const { createNewTableRecord } = useCreateNewTableRecord(recordIndexId);
const handleCreateRecord = () => {
createNewTableRecord();
};
const { indexIdentifierUrl } = useHandleIndexIdentifierClick({ const { indexIdentifierUrl } = useHandleIndexIdentifierClick({
objectMetadataItem, objectMetadataItem,
recordIndexId, recordIndexId,
@ -63,7 +56,7 @@ export const RecordIndexPage = () => {
return ( return (
<PageContainer> <PageContainer>
<RecordIndexRootPropsContext.Provider <RecordIndexContextProvider
value={{ value={{
recordIndexId, recordIndexId,
objectNamePlural, objectNamePlural,
@ -71,7 +64,6 @@ export const RecordIndexPage = () => {
objectMetadataItem, objectMetadataItem,
onIndexRecordsLoaded: handleIndexRecordsLoaded, onIndexRecordsLoaded: handleIndexRecordsLoaded,
indexIdentifierUrl, indexIdentifierUrl,
onCreateRecord: handleCreateRecord,
}} }}
> >
<ViewComponentInstanceContext.Provider <ViewComponentInstanceContext.Provider
@ -100,7 +92,7 @@ export const RecordIndexPage = () => {
</ActionMenuComponentInstanceContext.Provider> </ActionMenuComponentInstanceContext.Provider>
</ContextStoreComponentInstanceContext.Provider> </ContextStoreComponentInstanceContext.Provider>
</ViewComponentInstanceContext.Provider> </ViewComponentInstanceContext.Provider>
</RecordIndexRootPropsContext.Provider> </RecordIndexContextProvider>
</PageContainer> </PageContainer>
); );
}; };

View File

@ -2,7 +2,8 @@ import { Decorator } from '@storybook/react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState'; import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext'; import { RecordTableBodyContextProvider } from '@/object-record/record-table/contexts/RecordTableBodyContext';
import { RecordTableContextProvider } from '@/object-record/record-table/contexts/RecordTableContext';
import { isDefined } from 'twenty-ui'; import { isDefined } from 'twenty-ui';
export const RecordTableDecorator: Decorator = (Story) => { export const RecordTableDecorator: Decorator = (Story) => {
@ -17,23 +18,28 @@ export const RecordTableDecorator: Decorator = (Story) => {
} }
return ( return (
<RecordTableContext.Provider <RecordTableContextProvider
value={{ value={{
objectNameSingular: personObjectMetadataItem?.nameSingular, objectNameSingular: personObjectMetadataItem.nameSingular,
objectMetadataItem: personObjectMetadataItem, objectMetadataItem: personObjectMetadataItem,
onCellMouseEnter: () => {},
onCloseTableCell: () => {},
onOpenTableCell: () => {},
onActionMenuDropdownOpened: () => {},
onMoveFocus: () => {},
onMoveSoftFocusToCell: () => {},
onUpsertRecord: () => {},
recordTableId: 'persons', recordTableId: 'persons',
viewBarId: 'view-bar', viewBarId: 'view-bar',
visibleTableColumns: [], visibleTableColumns: [],
}} }}
> >
<Story /> <RecordTableBodyContextProvider
</RecordTableContext.Provider> value={{
onCellMouseEnter: () => {},
onCloseTableCell: () => {},
onOpenTableCell: () => {},
onActionMenuDropdownOpened: () => {},
onMoveFocus: () => {},
onMoveSoftFocusToCell: () => {},
onUpsertRecord: () => {},
}}
>
<Story />
</RecordTableBodyContextProvider>
</RecordTableContextProvider>
); );
}; };

View File

@ -0,0 +1,20 @@
import React, { useContext } from 'react';
export const createRequiredContext = <TContext>(debugName: string) => {
const Context = React.createContext<TContext | undefined>(undefined);
Context.displayName = `${debugName}Provider`;
const useRequiredContextOrThrow = (): TContext => {
const context = useContext(Context);
if (context === undefined) {
throw new Error(
`${debugName} Context not found. Please wrap your component tree with <${Context.displayName}> before using use${debugName}OrThrow().`,
);
}
return context;
};
return [Context.Provider, useRequiredContextOrThrow] as const;
};

View File

@ -1,11 +0,0 @@
import { Context, createContext } from 'react';
type RootProps = Record<string, any>;
export type RootPropsContext<T extends RootProps> = T extends RootProps
? T
: never;
export const createRootPropsContext = <T extends RootProps>(): Context<
RootPropsContext<T>
> => createContext<RootPropsContext<T>>({} as RootPropsContext<T>);