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
@@ -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 { useSelectFilter } from '@/object-record/object-filter-dropdown/hooks/useSelectFilter';
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 { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
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 { availableFilterDefinitionsComponentState } from '@/views/states/availableFilterDefinitionsComponentState';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-ui';
@@ -57,7 +56,7 @@ type ObjectFilterDropdownFilterSelectProps = {
export const ObjectFilterDropdownFilterSelect = ({
isAdvancedFilterButtonVisible,
}: ObjectFilterDropdownFilterSelectProps) => {
const { recordIndexId } = useContext(RecordIndexRootPropsContext);
const { recordIndexId } = useRecordIndexContextOrThrow();
const {
setObjectFilterDropdownSearchInput,
@@ -1,9 +1,12 @@
import { Meta, StoryObj } from '@storybook/react';
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 { ObjectFilterDropdownScope } from '@/object-record/object-filter-dropdown/scopes/ObjectFilterDropdownScope';
import { ObjectFilterDropdownComponentInstanceContext } from '@/object-record/object-filter-dropdown/states/contexts/ObjectFilterDropdownComponentInstanceContext';
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
import { ColumnDefinition } from '@/object-record/record-table/types/ColumnDefinition';
@@ -19,6 +22,7 @@ import { FieldMetadataType } from '~/generated/graphql';
import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator';
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
const meta: Meta<typeof MultipleFiltersDropdownButton> = {
title:
@@ -26,6 +30,9 @@ const meta: Meta<typeof MultipleFiltersDropdownButton> = {
component: MultipleFiltersDropdownButton,
decorators: [
(Story) => {
const companyObjectMetadataItem = generatedMockObjectMetadataItems.find(
(item) => item.nameSingular === CoreObjectNameSingular.Company,
)!;
const instanceId = 'entity-tasks-filter-scope';
const setAvailableFilterDefinitions = useSetRecoilComponentStateV2(
availableFilterDefinitionsComponentState,
@@ -91,6 +98,16 @@ const meta: Meta<typeof MultipleFiltersDropdownButton> = {
},
]);
return (
<RecordIndexContextProvider
value={{
indexIdentifierUrl: () => '',
onIndexRecordsLoaded: () => {},
objectNamePlural: CoreObjectNamePlural.Company,
objectNameSingular: CoreObjectNameSingular.Company,
objectMetadataItem: companyObjectMetadataItem,
recordIndexId: instanceId,
}}
>
<ObjectFilterDropdownComponentInstanceContext.Provider
value={{ instanceId }}
>
@@ -104,6 +121,7 @@ const meta: Meta<typeof MultipleFiltersDropdownButton> = {
</ViewComponentInstanceContext.Provider>
</RecordTableComponentInstanceContext.Provider>
</ObjectFilterDropdownComponentInstanceContext.Provider>
</RecordIndexContextProvider>
);
},
ObjectMetadataItemsDecorator,
@@ -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 { ObjectOptionsDropdownContext } from '@/object-record/object-options-dropdown/states/contexts/ObjectOptionsDropdownContext';
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 { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
@@ -76,11 +76,10 @@ const createStory = (contentId: ObjectOptionsContentId | null): Story => ({
)!;
return (
<RecordIndexRootPropsContext.Provider
<RecordIndexContextProvider
value={{
indexIdentifierUrl: () => '',
onIndexRecordsLoaded: () => {},
onCreateRecord: () => {},
objectNamePlural: 'companies',
objectNameSingular: 'company',
objectMetadataItem: companyObjectMetadataItem,
@@ -102,7 +101,7 @@ const createStory = (contentId: ObjectOptionsContentId | null): Story => ({
<Story />
</DropdownMenu>
</ObjectOptionsDropdownContext.Provider>
</RecordIndexRootPropsContext.Provider>
</RecordIndexContextProvider>
);
},
],
@@ -1,5 +1,5 @@
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 { renderHook } from '@testing-library/react';
import { act } from 'react';
@@ -11,13 +11,13 @@ describe('useSearchRecordGroupField', () => {
renderHook(() => useSearchRecordGroupField(), {
wrapper: ({ children }) => (
<RecoilRoot>
<RecordIndexRootPropsContext.Provider value={contextValue}>
<RecordIndexContextProvider value={contextValue}>
<ViewComponentInstanceContext.Provider
value={{ instanceId: 'myViewInstanceId' }}
>
{children}
</ViewComponentInstanceContext.Provider>
</RecordIndexRootPropsContext.Provider>
</RecordIndexContextProvider>
</RecoilRoot>
),
});
@@ -1,11 +1,11 @@
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 { useContext, useMemo } from 'react';
import { useMemo } from 'react';
import { FieldMetadataType } from '~/generated-metadata/graphql';
export const useSearchRecordGroupField = () => {
const { objectMetadataItem } = useContext(RecordIndexRootPropsContext);
const { objectMetadataItem } = useRecordIndexContextOrThrow();
const [recordGroupFieldSearchInput, setRecordGroupFieldSearchInput] =
useRecoilComponentStateV2(objectOptionsDropdownSearchInputComponentState);
@@ -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 { useObjectSortDropdown } from '@/object-record/object-sort-dropdown/hooks/useObjectSortDropdown';
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 { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
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 { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useContext } from 'react';
import { SORT_DIRECTIONS } from '../types/SortDirection';
export const StyledInput = styled.input`
@@ -77,7 +76,7 @@ export const ObjectSortDropdownButton = ({
resetSearchInput,
} = useObjectSortDropdown();
const { recordIndexId } = useContext(RecordIndexRootPropsContext);
const { recordIndexId } = useRecordIndexContextOrThrow();
const { isDropdownOpen } = useDropdown(OBJECT_SORT_DROPDOWN_ID);
@@ -1,15 +1,41 @@
import { Meta, StoryObj } from '@storybook/react';
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 { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
import { ChipGeneratorsDecorator } from '~/testing/decorators/ChipGeneratorsDecorator';
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
const meta: Meta = {
title: 'UI/Data/Field/Display/ChipFieldDisplay',
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,
ChipGeneratorsDecorator,
getFieldDecorator('person', 'name'),
@@ -1,7 +1,7 @@
import { RecordGroupContext } from '@/object-record/record-group/states/context/RecordGroupContext';
import { useContext } from 'react';
export const useCurrentRecordGroupId = () => {
export const useCurrentRecordGroupId = (): string => {
const context = useContext(RecordGroupContext);
if (!context) {
@@ -5,7 +5,7 @@ import { RecordBoardColumnContext } from '@/object-record/record-board/record-bo
import { useRecordGroupVisibility } from '@/object-record/record-group/hooks/useRecordGroupVisibility';
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
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 { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useCallback, useContext, useMemo } from 'react';
@@ -17,9 +17,7 @@ export const useRecordGroupActions = () => {
const navigate = useNavigate();
const location = useLocation();
const { objectNameSingular, recordIndexId } = useContext(
RecordIndexRootPropsContext,
);
const { objectNameSingular, recordIndexId } = useRecordIndexContextOrThrow();
const { columnDefinition: recordGroupDefinition } = useContext(
RecordBoardColumnContext,
@@ -2,15 +2,14 @@ import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/s
import { recordGroupFieldMetadataComponentState } from '@/object-record/record-group/states/recordGroupFieldMetadataComponentState';
import { recordGroupIdsComponentState } from '@/object-record/record-group/states/recordGroupIdsComponentState';
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 { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { useContext } from 'react';
import { useRecoilCallback } from 'recoil';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
export const useSetRecordGroup = (viewId?: string) => {
const { objectMetadataItem } = useContext(RecordIndexRootPropsContext);
const { objectMetadataItem } = useRecordIndexContextOrThrow();
const recordIndexRecordGroupIdsState = useRecoilComponentCallbackStateV2(
recordGroupIdsComponentState,
@@ -17,7 +17,7 @@ import { recordIndexSortsState } from '@/object-record/record-index/states/recor
import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState';
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 { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
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 { mapViewSortsToSorts } from '@/views/utils/mapViewSortsToSorts';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useCallback, useContext } from 'react';
import { useCallback } from 'react';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
const StyledContainer = styled.div`
@@ -67,7 +67,7 @@ export const RecordIndexContainer = () => {
recordIndexId,
objectMetadataItem,
objectNameSingular,
} = useContext(RecordIndexRootPropsContext);
} = useRecordIndexContextOrThrow();
const setRecordGroup = useSetRecordGroup(recordIndexId);
@@ -5,11 +5,11 @@ import { computeContextStoreFilters } from '@/context-store/utils/computeContext
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
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 { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
import { useContext, useEffect } from 'react';
import { useEffect } from 'react';
export const RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect =
() => {
@@ -21,7 +21,7 @@ export const RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect =
contextStoreTargetedRecordsRuleComponentState,
);
const { objectNamePlural } = useContext(RecordIndexRootPropsContext);
const { objectNamePlural } = useRecordIndexContextOrThrow();
const { objectNameSingular } = useObjectNameSingularFromPlural({
objectNamePlural,
@@ -1,15 +1,15 @@
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
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 { useContext, useEffect } from 'react';
import { useEffect } from 'react';
export const RecordIndexContainerContextStoreObjectMetadataEffect = () => {
const setContextStoreCurrentObjectMetadataItem = useSetRecoilComponentStateV2(
contextStoreCurrentObjectMetadataIdComponentState,
);
const { objectNamePlural } = useContext(RecordIndexRootPropsContext);
const { objectNamePlural } = useRecordIndexContextOrThrow();
const { objectNameSingular } = useObjectNameSingularFromPlural({
objectNamePlural,
@@ -1,8 +1,8 @@
import { useContext, useEffect } from 'react';
import { useEffect } from 'react';
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
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 { hasUserSelectedAllRowsComponentState } from '@/object-record/record-table/record-table-row/states/hasUserSelectedAllRowsFamilyState';
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';
export const RecordIndexFiltersToContextStoreEffect = () => {
const { recordIndexId } = useContext(RecordIndexRootPropsContext);
const { recordIndexId } = useRecordIndexContextOrThrow();
const recordIndexFilters = useRecoilValue(recordIndexFiltersState);
@@ -3,16 +3,14 @@ import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-sto
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { isObjectMetadataReadOnly } from '@/object-metadata/utils/isObjectMetadataReadOnly';
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 { 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 { PageHotkeysEffect } from '@/ui/layout/page/components/PageHotkeysEffect';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { ViewType } from '@/views/types/ViewType';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
import { isDefined, useIcons } from 'twenty-ui';
import { capitalize } from '~/utils/string/capitalize';
@@ -21,17 +19,13 @@ export const RecordIndexPageHeader = () => {
const { findObjectMetadataItemByNamePlural } =
useFilteredObjectMetadataItems();
const { objectNamePlural, onCreateRecord } = useContext(
RecordIndexRootPropsContext,
);
const { objectNamePlural } = useRecordIndexContextOrThrow();
const objectMetadataItem =
findObjectMetadataItemByNamePlural(objectNamePlural);
const { getIcon } = useIcons();
const Icon = getIcon(
findObjectMetadataItemByNamePlural(objectNamePlural)?.icon,
);
const Icon = getIcon(objectMetadataItem?.icon);
const recordIndexViewType = useRecoilValue(recordIndexViewTypeState);
@@ -56,17 +50,14 @@ export const RecordIndexPageHeader = () => {
const pageHeaderTitle =
objectMetadataItem?.labelPlural ?? capitalize(objectNamePlural);
const handleAddButtonClick = () => {
onCreateRecord();
};
return (
<PageHeader title={pageHeaderTitle} Icon={Icon}>
<PageHotkeysEffect onAddButtonClick={handleAddButtonClick} />
{shouldDisplayAddButton &&
/**
* TODO: Logic between Table and Kanban should be merged here when we move some states to record-index
*/
(isTable ? (
<PageAddButton onClick={handleAddButtonClick} />
<RecordIndexPageTableAddButton />
) : (
<RecordIndexPageKanbanAddButton />
))}
@@ -1,4 +1,3 @@
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard';
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 { RecordGroupDefinition } from '@/object-record/record-group/types/RecordGroupDefinition';
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 { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
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 { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import styled from '@emotion/styled';
import { useCallback, useContext } from 'react';
import { useCallback } from 'react';
import { useRecoilValue } from 'recoil';
const StyledDropdownMenuItemsContainer = styled(DropdownMenuItemsContainer)`
width: 100%;
`;
const StyledDropDownMenu = styled(DropdownMenu)`
width: 200px;
`;
export const RecordIndexPageKanbanAddButton = () => {
const dropdownId = `record-index-page-add-button-dropdown`;
const { recordIndexId, objectNameSingular } = useContext(
RecordIndexRootPropsContext,
);
const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular });
const { recordIndexId, objectMetadataItem } = useRecordIndexContextOrThrow();
const visibleRecordGroupIds = useRecoilComponentValueV2(
visibleRecordGroupIdsComponentSelector,
@@ -95,8 +81,7 @@ export const RecordIndexPageKanbanAddButton = () => {
clickableComponent={<PageAddButton />}
dropdownId={dropdownId}
dropdownComponents={
<StyledDropDownMenu>
<StyledDropdownMenuItemsContainer>
<DropdownMenuItemsContainer>
{visibleRecordGroupIds.map((recordGroupId) => (
<RecordIndexPageKanbanAddMenuItem
key={recordGroupId}
@@ -104,8 +89,7 @@ export const RecordIndexPageKanbanAddButton = () => {
onItemClick={handleItemClick}
/>
))}
</StyledDropdownMenuItemsContainer>
</StyledDropDownMenu>
</DropdownMenuItemsContainer>
}
dropdownHotkeyScope={{ scope: dropdownId }}
/>
@@ -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 />;
};
@@ -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 }}
/>
);
};
@@ -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} />
</>
);
};
@@ -1,8 +1,7 @@
import { useGetStandardObjectIcon } from '@/object-metadata/hooks/useGetStandardObjectIcon';
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 { useContext } from 'react';
import { AvatarChip, AvatarChipVariant, ChipSize } from 'twenty-ui';
export type RecordIdentifierChipProps = {
@@ -20,7 +19,7 @@ export const RecordIdentifierChip = ({
size,
maxWidth,
}: RecordIdentifierChipProps) => {
const { indexIdentifierUrl } = useContext(RecordIndexRootPropsContext);
const { indexIdentifierUrl } = useRecordIndexContextOrThrow();
const { recordChipData } = useRecordChipData({
objectNameSingular,
record,
@@ -1,9 +1,8 @@
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { RecordUpdateHookParams } from '@/object-record/record-field/contexts/FieldContext';
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 { useContext } from 'react';
type RecordIndexTableContainerProps = {
recordTableId: string;
@@ -14,7 +13,7 @@ export const RecordIndexTableContainer = ({
recordTableId,
viewBarId,
}: RecordIndexTableContainerProps) => {
const { objectNameSingular } = useContext(RecordIndexRootPropsContext);
const { objectNameSingular } = useRecordIndexContextOrThrow();
const { updateOneRecord } = useUpdateOneRecord({
objectNameSingular,
@@ -1,8 +1,8 @@
import { useContext, useEffect } from 'react';
import { useEffect } from 'react';
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
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 { useHandleToggleColumnSort } from '@/object-record/record-index/hooks/useHandleToggleColumnSort';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
@@ -13,9 +13,7 @@ import { ViewField } from '@/views/types/ViewField';
import { useRecoilCallback } from 'recoil';
export const RecordIndexTableContainerEffect = () => {
const { recordIndexId, objectNameSingular } = useContext(
RecordIndexRootPropsContext,
);
const { recordIndexId, objectNameSingular } = useRecordIndexContextOrThrow();
const viewBarId = recordIndexId;
@@ -1,15 +1,14 @@
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;
onIndexRecordsLoaded: () => void;
onCreateRecord: () => void;
objectNamePlural: string;
objectNameSingular: string;
objectMetadataItem: ObjectMetadataItem;
recordIndexId: string;
};
export const RecordIndexRootPropsContext =
createRootPropsContext<RecordIndexRootPropsContextProps>();
export const [RecordIndexContextProvider, useRecordIndexContextOrThrow] =
createRequiredContext<RecordIndexContextValue>('RecordIndexContext');
@@ -3,10 +3,9 @@ import { isNonEmptyString, isNull } from '@sniptt/guards';
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
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 { 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 { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { RecordTableBodyUnselectEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyUnselectEffect';
@@ -29,19 +28,9 @@ const StyledTable = styled.table`
width: 100%;
`;
type RecordTableProps = {
viewBarId: string;
recordTableId: string;
objectNameSingular: string;
onColumnsChange: (columns: any) => void;
};
export const RecordTable = () => {
const { recordTableId, objectNameSingular } = useRecordTableContextOrThrow();
export const RecordTable = ({
viewBarId,
recordTableId,
objectNameSingular,
onColumnsChange,
}: RecordTableProps) => {
const tableBodyRef = useRef<HTMLTableElement>(null);
const { toggleClickOutsideListener } = useClickOutsideListener(
@@ -82,30 +71,19 @@ export const RecordTable = ({
}
return (
<RecordTableComponentInstance
recordTableId={recordTableId}
onColumnsChange={onColumnsChange}
>
<RecordTableContextProvider
objectNameSingular={objectNameSingular}
recordTableId={recordTableId}
viewBarId={viewBarId}
>
<>
{!hasRecordGroups ? (
<RecordTableNoRecordGroupBodyEffect />
) : (
<RecordTableRecordGroupBodyEffects />
)}
<RecordTableBodyUnselectEffect
tableBodyRef={tableBodyRef}
recordTableId={recordTableId}
/>
<RecordTableBodyUnselectEffect tableBodyRef={tableBodyRef} />
{recordTableIsEmpty ? (
<RecordTableEmptyState />
) : (
<>
<StyledTable className="entity-table-cell" ref={tableBodyRef}>
<RecordTableHeader objectNameSingular={objectNameSingular} />
<RecordTableHeader />
{!hasRecordGroups ? (
<RecordTableNoRecordGroupBody />
) : (
@@ -126,7 +104,6 @@ export const RecordTable = ({
/>
</>
)}
</RecordTableContextProvider>
</RecordTableComponentInstance>
</>
);
};
@@ -1,117 +1,44 @@
import { ReactNode } from 'react';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { RecordTableContext } 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 { RecordTableContextProvider as RecordTableContextInternalProvider } from '@/object-record/record-table/contexts/RecordTableContext';
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';
type RecordTableContextProviderProps = {
viewBarId: string;
recordTableId: string;
objectNameSingular: string;
children: ReactNode;
};
export const RecordTableContextProvider = ({
viewBarId,
recordTableId,
objectNameSingular,
children,
}: {
viewBarId: string;
recordTableId: string;
objectNameSingular: string;
children: ReactNode;
}) => {
}: RecordTableContextProviderProps) => {
const { objectMetadataItem } = useObjectMetadataItem({
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(
visibleTableColumnsComponentSelector,
recordTableId,
);
return (
<RecordTableContext.Provider
<RecordTableContextInternalProvider
value={{
viewBarId,
objectMetadataItem,
onUpsertRecord: handleUpsertRecord,
onOpenTableCell: handleOpenTableCell,
onMoveFocus: handleMoveFocus,
onCloseTableCell: handleCloseTableCell,
onMoveSoftFocusToCell: handleMoveSoftFocusToCell,
onActionMenuDropdownOpened: handleActionMenuDropdown,
onCellMouseEnter: handleContainerMouseEnter,
visibleTableColumns,
recordTableId,
objectNameSingular,
}}
>
{children}
</RecordTableContext.Provider>
</RecordTableContextInternalProvider>
);
};
@@ -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>
);
};
@@ -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>
);
};
@@ -1,7 +1,10 @@
import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
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 { 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 { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
@@ -30,9 +33,13 @@ export const RecordTableRecordGroupRows = () => {
[allRecordIds],
);
if (!isRecordGroupTableSectionToggled) {
return null;
}
return (
isRecordGroupTableSectionToggled &&
recordIdsByGroup.map((recordId, rowIndexInGroup) => {
<>
{recordIdsByGroup.map((recordId, rowIndexInGroup) => {
const rowIndex = rowIndexMap.get(recordId);
if (!isDefined(rowIndex)) {
@@ -48,6 +55,10 @@ export const RecordTableRecordGroupRows = () => {
isPendingRow={!isRecordGroupTableSectionToggled}
/>
);
})
})}
<RecordTableRecordGroupSectionLoadMore />
<RecordTablePendingRecordGroupRow />
<RecordTableRecordGroupSectionAddNew />
</>
);
};
@@ -16,6 +16,8 @@ import { RecordUpdateContext } from '../contexts/EntityUpdateMutationHookContext
import { useRecordTable } from '../hooks/useRecordTable';
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 { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { Key } from 'ts-key-enum';
@@ -96,6 +98,15 @@ export const RecordTableWithWrappers = ({
);
return (
<RecordTableComponentInstance
recordTableId={recordTableId}
onColumnsChange={handleColumnsChange}
>
<RecordTableContextProvider
recordTableId={recordTableId}
viewBarId={viewBarId}
objectNameSingular={objectNameSingular}
>
<EntityDeleteContext.Provider value={deleteOneRecord}>
<ScrollWrapper
enableXScroll={isScrollEnabledForRecordTable.enableXScroll}
@@ -106,17 +117,14 @@ export const RecordTableWithWrappers = ({
<StyledTableWithHeader>
<StyledTableContainer>
<StyledTableInternalContainer>
<RecordTable
viewBarId={viewBarId}
recordTableId={recordTableId}
objectNameSingular={objectNameSingular}
onColumnsChange={handleColumnsChange}
/>
<RecordTable />
</StyledTableInternalContainer>
</StyledTableContainer>
</StyledTableWithHeader>
</RecordUpdateContext.Provider>
</ScrollWrapper>
</EntityDeleteContext.Provider>
</RecordTableContextProvider>
</RecordTableComponentInstance>
);
};
@@ -14,12 +14,13 @@ import {
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
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 { ChipGeneratorsDecorator } from '~/testing/decorators/ChipGeneratorsDecorator';
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
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 { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
import { mockPerformance } from './mock';
@@ -61,10 +62,22 @@ const meta: Meta = {
(Story) => {
return (
<RecordFieldValueSelectorContextProvider>
<RecordTableContext.Provider
<RecordTableContextProvider
value={{
recordTableId: 'recordTableId',
viewBarId: mockPerformance.recordId,
objectMetadataItem: mockPerformance.objectMetadataItem as any,
visibleTableColumns: mockPerformance.visibleTableColumns as any,
objectNameSingular:
mockPerformance.objectMetadataItem.nameSingular,
}}
>
<RecordTableComponentInstance
recordTableId="asd"
onColumnsChange={() => {}}
>
<RecordTableBodyContextProvider
value={{
onUpsertRecord: () => {},
onOpenTableCell: () => {},
onMoveFocus: () => {},
@@ -72,15 +85,7 @@ const meta: Meta = {
onMoveSoftFocusToCell: () => {},
onActionMenuDropdownOpened: () => {},
onCellMouseEnter: () => {},
visibleTableColumns: mockPerformance.visibleTableColumns as any,
objectNameSingular:
mockPerformance.objectMetadataItem.nameSingular,
recordTableId: 'recordTableId',
}}
>
<RecordTableComponentInstance
recordTableId="asd"
onColumnsChange={() => {}}
>
<RecordTableRowContext.Provider
value={{
@@ -131,8 +136,9 @@ const meta: Meta = {
</FieldContext.Provider>
</RecordTableCellContext.Provider>
</RecordTableRowContext.Provider>
</RecordTableBodyContextProvider>
</RecordTableComponentInstance>
</RecordTableContext.Provider>
</RecordTableContextProvider>
</RecordFieldValueSelectorContextProvider>
);
},
@@ -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',
);
@@ -1,39 +1,15 @@
import React, { createContext } from 'react';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
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 { MoveFocusDirection } from '@/object-record/record-table/types/MoveFocusDirection';
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
import { createRequiredContext } from '~/utils/createRequiredContext';
export type RecordTableContextProps = {
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>[];
export type RecordTableContextValue = {
recordTableId: string;
viewBarId: string;
objectNameSingular: string;
objectMetadataItem: ObjectMetadataItem;
visibleTableColumns: ColumnDefinition<FieldMetadata>[];
};
export const RecordTableContext = createContext<RecordTableContextProps>(
{} as RecordTableContextProps,
);
export const [RecordTableContextProvider, useRecordTableContextOrThrow] =
createRequiredContext<RecordTableContextValue>('RecordTableContext');
@@ -11,6 +11,7 @@ export type RecordTableRowContextProps = {
isDragging: boolean;
dragHandleProps: DraggableProvidedDragHandleProps | null;
inView?: boolean;
isDragDisabled?: boolean;
};
export const RecordTableRowContext = createContext<RecordTableRowContextProps>(
@@ -1,16 +1,15 @@
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 { RecordTableEmptyStateNoRecordFoundForFilter } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordFoundForFilter';
import { RecordTableEmptyStateRemote } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateRemote';
import { RecordTableEmptyStateSoftDelete } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete';
import { isSoftDeleteFilterActiveComponentState } from '@/object-record/record-table/states/isSoftDeleteFilterActiveComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useContext } from 'react';
export const RecordTableEmptyState = () => {
const { objectNameSingular, recordTableId, objectMetadataItem } =
useContext(RecordTableContext);
const { recordTableId, objectNameSingular, objectMetadataItem } =
useRecordTableContextOrThrow();
const { totalCount } = useFindManyRecords({ objectNameSingular, limit: 1 });
const noRecordAtAll = totalCount === 0;
@@ -1,6 +1,5 @@
import { isObjectMetadataReadOnly } from '@/object-metadata/utils/isObjectMetadataReadOnly';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
import { useContext } from 'react';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import {
AnimatedPlaceholder,
AnimatedPlaceholderEmptyContainer,
@@ -29,7 +28,7 @@ export const RecordTableEmptyStateDisplay = ({
subTitle,
title,
}: RecordTableEmptyStateDisplayProps) => {
const { objectMetadataItem } = useContext(RecordTableContext);
const { objectMetadataItem } = useRecordTableContextOrThrow();
const isReadOnly = isObjectMetadataReadOnly(objectMetadataItem);
return (
@@ -1,15 +1,14 @@
import { IconPlus } from 'twenty-ui';
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 { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords';
import { useContext } from 'react';
export const RecordTableEmptyStateNoRecordAtAll = () => {
const { createNewTableRecord } = useCreateNewTableRecord();
const { objectMetadataItem, recordTableId } = useRecordTableContextOrThrow();
const { objectMetadataItem } = useContext(RecordTableContext);
const { createNewTableRecord } = useCreateNewTableRecord(recordTableId);
const handleButtonClick = () => {
createNewTableRecord();
@@ -1,15 +1,14 @@
import { IconPlus } from 'twenty-ui';
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 { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords';
import { useContext } from 'react';
export const RecordTableEmptyStateNoRecordFoundForFilter = () => {
const { createNewTableRecord } = useCreateNewTableRecord();
const { recordTableId, objectMetadataItem } = useRecordTableContextOrThrow();
const { objectMetadataItem } = useContext(RecordTableContext);
const { createNewTableRecord } = useCreateNewTableRecord(recordTableId);
const handleButtonClick = () => {
createNewTableRecord();
@@ -2,16 +2,15 @@ import { IconFilterOff } from 'twenty-ui';
import { useObjectLabel } from '@/object-metadata/hooks/useObjectLabel';
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 { tableFiltersComponentState } from '@/object-record/record-table/states/tableFiltersComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useDeleteCombinedViewFilters } from '@/views/hooks/useDeleteCombinedViewFilters';
import { useContext } from 'react';
export const RecordTableEmptyStateSoftDelete = () => {
const { objectMetadataItem, objectNameSingular, recordTableId } =
useContext(RecordTableContext);
useRecordTableContextOrThrow();
const { deleteCombinedViewFilter } =
useDeleteCombinedViewFilters(recordTableId);
@@ -1,5 +1,5 @@
import { act, renderHook } from '@testing-library/react';
import { ReactNode } from 'react';
import { renderHook } from '@testing-library/react';
import { ReactNode, act } from 'react';
import { RecoilRoot } from 'recoil';
import { createState } from 'twenty-ui';
@@ -9,12 +9,16 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
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 { 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 { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState';
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/generatedMockObjectMetadataItems';
const draftValue = 'updated Name';
const recordGroupId = 'recordGroupId';
// Todo refactor this test to inject the states in a cleaner way instead of mocking hooks
// (this is not easy to maintain while refactoring)
@@ -37,8 +41,11 @@ jest.mock(
}),
);
const pendingRecordIdState = createState<string | null>({
key: 'pendingRecordIdState',
const recordTablePendingRecordIdByGroupComponentFamilyState = createFamilyState<
string | null,
string
>({
key: 'recordTablePendingRecordIdByGroupComponentFamilyState',
defaultValue: null,
});
@@ -60,9 +67,23 @@ const Wrapper = ({
<RecoilRoot
initializeState={(snapshot) => {
snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems);
snapshot.set(pendingRecordIdState, pendingRecordIdMockedValue);
snapshot.set(
recordTablePendingRecordIdByGroupComponentFamilyState(recordGroupId),
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 }}
@@ -84,22 +105,26 @@ const Wrapper = ({
{children}
</FieldContext.Provider>
</ViewComponentInstanceContext.Provider>
</RecordTableComponentInstanceContext.Provider>
</RecordTableContextProvider>
</RecoilRoot>
);
describe('useUpsertRecord', () => {
describe('useUpsertTableRecordInGroup', () => {
beforeEach(async () => {
createOneRecordMock.mockClear();
updateOneRecordMock.mockClear();
});
it('calls update record if there is no pending record', async () => {
const { result } = renderHook(
() =>
useUpsertRecord({
/**
* {
objectNameSingular: 'person',
recordTableId: 'recordTableId',
}),
}
*/
const { result } = renderHook(
() => useUpsertTableRecordInGroup(recordGroupId),
{
wrapper: ({ children }) =>
Wrapper({
@@ -111,7 +136,7 @@ describe('useUpsertRecord', () => {
);
await act(async () => {
await result.current.upsertRecord(
await result.current.upsertTableRecordInGroup(
updateOneRecordMock,
'recordId',
'name',
@@ -124,11 +149,7 @@ describe('useUpsertRecord', () => {
it('calls update record if pending record is empty', async () => {
const { result } = renderHook(
() =>
useUpsertRecord({
objectNameSingular: 'person',
recordTableId: 'recordTableId',
}),
() => useUpsertTableRecordInGroup(recordGroupId),
{
wrapper: ({ children }) =>
Wrapper({
@@ -140,7 +161,7 @@ describe('useUpsertRecord', () => {
);
await act(async () => {
await result.current.upsertRecord(
await result.current.upsertTableRecordInGroup(
updateOneRecordMock,
'recordId',
'name',
@@ -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();
});
});
@@ -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 };
};
@@ -1,32 +1,22 @@
import { useRecoilCallback } from 'recoil';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
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 { 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 { 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 { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { extractComponentSelector } from '@/ui/utilities/state/component-state/utils/extractComponentSelector';
import { isDefined } from '~/utils/isDefined';
export const useUpsertRecord = ({
objectNameSingular,
recordTableId,
}: {
objectNameSingular: string;
recordTableId: string;
}) => {
const hasRecordGroups = useRecoilComponentValueV2(
hasRecordGroupsComponentSelector,
);
export const useUpsertTableRecordNoGroup = () => {
const { objectMetadataItem, objectNameSingular, recordTableId } =
useRecordTableContextOrThrow();
const { createOneRecord } = useCreateOneRecord({
objectNameSingular,
shouldMatchRootQueryFilter: hasRecordGroups,
});
const recordTablePendingRecordIdState = useRecoilComponentCallbackStateV2(
@@ -34,28 +24,12 @@ export const useUpsertRecord = ({
recordTableId,
);
const upsertRecord = useRecoilCallback(
const upsertTableRecordNoGroup = useRecoilCallback(
({ snapshot }) =>
(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 =
getLabelIdentifierFieldMetadataItem(foundObjectMetadataItem);
getLabelIdentifierFieldMetadataItem(objectMetadataItem);
const recordTablePendingRecordId = getSnapshotValue(
snapshot,
recordTablePendingRecordIdState,
);
const fieldScopeId = getScopeIdFromComponentId(
`${recordId}-${fieldName}`,
);
@@ -67,6 +41,11 @@ export const useUpsertRecord = ({
const draftValue = getSnapshotValue(snapshot, draftValueSelector());
const recordTablePendingRecordId = getSnapshotValue(
snapshot,
recordTablePendingRecordIdState,
);
if (isDefined(recordTablePendingRecordId) && isDefined(draftValue)) {
createOneRecord({
id: recordTablePendingRecordId,
@@ -77,8 +56,8 @@ export const useUpsertRecord = ({
persistField();
}
},
[createOneRecord, objectNameSingular, recordTablePendingRecordIdState],
[createOneRecord, objectMetadataItem, recordTablePendingRecordIdState],
);
return { upsertRecord };
return { upsertTableRecordNoGroup };
};
@@ -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,
};
};
@@ -1,33 +1,94 @@
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
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 { 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 { 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 { isDefined } from '~/utils/isDefined';
export const useCreateNewTableRecord = (recordTableIdFromProps?: string) => {
const { recordTableId } = useContext(RecordTableContext);
const recordTableIdToUse = recordTableIdFromProps ?? recordTableId;
export const useCreateNewTableRecord = (recordTableId: string) => {
const { objectMetadataItem } = useRecordIndexContextOrThrow();
const { setSelectedTableCellEditMode } = useSelectedTableCellEditMode({
scopeId: recordTableIdToUse,
scopeId: recordTableId,
});
const setHotkeyScope = useSetHotkeyScope();
const { setPendingRecordId } = useRecordTable({
recordTableId: recordTableIdToUse,
});
const setPendingRecordId = useSetRecoilComponentStateV2(
recordTablePendingRecordIdComponentState,
recordTableId,
);
const recordTablePendingRecordIdByGroupFamilyState =
useRecoilComponentCallbackStateV2(
recordTablePendingRecordIdByGroupComponentFamilyState,
recordTableId,
);
const { setActiveDropdownFocusIdAndMemorizePrevious } =
useSetActiveDropdownFocusIdAndMemorizePrevious();
const createNewTableRecord = () => {
setPendingRecordId(v4());
const recordId = v4();
setPendingRecordId(recordId);
setSelectedTableCellEditMode(-1, 0);
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 {
createNewTableRecord,
createNewTableRecordInGroup,
};
};
@@ -22,7 +22,6 @@ import { onColumnsChangeComponentState } from '@/object-record/record-table/stat
import { onEntityCountChangeComponentState } from '@/object-record/record-table/states/onEntityCountChangeComponentState';
import { onToggleColumnFilterComponentState } from '@/object-record/record-table/states/onToggleColumnFilterComponentState';
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 { tableFiltersComponentState } from '@/object-record/record-table/states/tableFiltersComponentState';
import { tableLastRowVisibleComponentState } from '@/object-record/record-table/states/tableLastRowVisibleComponentState';
@@ -240,11 +239,6 @@ export const useRecordTable = (props?: useRecordTableProps) => {
const isSomeCellInEditModeState =
useGetIsSomeCellInEditModeState(recordTableId);
const setPendingRecordId = useSetRecoilComponentStateV2(
recordTablePendingRecordIdComponentState,
recordTableId,
);
return {
onColumnsChange,
setAvailableTableColumns,
@@ -272,6 +266,5 @@ export const useRecordTable = (props?: useRecordTableProps) => {
setHasUserSelectedAllRows,
setOnToggleColumnFilter,
setOnToggleColumnSort,
setPendingRecordId,
};
};
@@ -1,12 +1,12 @@
import { DragDropContext, DropResult } from '@hello-pangea/dnd';
import { ReactNode, useContext } from 'react';
import { ReactNode } from 'react';
import { useRecoilCallback, useSetRecoilState } from 'recoil';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { getDraggedRecordPosition } from '@/object-record/record-board/utils/getDraggedRecordPosition';
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
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 { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
@@ -18,7 +18,7 @@ export const RecordTableBodyDragDropContextProvider = ({
}: {
children: ReactNode;
}) => {
const { objectNameSingular, recordTableId } = useContext(RecordTableContext);
const { objectNameSingular, recordTableId } = useRecordTableContextOrThrow();
const { updateOneRecord: updateOneRow } = useUpdateOneRecord({
objectNameSingular,
@@ -1,5 +1,5 @@
import { DragDropContext, DropResult } from '@hello-pangea/dnd';
import { ReactNode, useContext } from 'react';
import { ReactNode } from 'react';
import { useRecoilCallback, useSetRecoilState } from 'recoil';
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 { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState';
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 { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
@@ -20,7 +20,7 @@ export const RecordTableBodyRecordGroupDragDropContextProvider = ({
children: ReactNode;
}) => {
const { objectNameSingular, recordTableId, objectMetadataItem } =
useContext(RecordTableContext);
useRecordTableContextOrThrow();
const { updateOneRecord: updateOneRow } = useUpdateOneRecord({
objectNameSingular,
@@ -1,6 +1,7 @@
import { Key } from 'ts-key-enum';
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 { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
@@ -9,13 +10,13 @@ import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useLis
type RecordTableBodyUnselectEffectProps = {
tableBodyRef: React.RefObject<HTMLDivElement>;
recordTableId: string;
};
export const RecordTableBodyUnselectEffect = ({
tableBodyRef,
recordTableId,
}: RecordTableBodyUnselectEffectProps) => {
const { recordTableId } = useRecordTableContextOrThrow();
const leaveTableFocus = useLeaveTableFocus(recordTableId);
const { resetTableRowSelection, useMapKeyboardToSoftFocus } = useRecordTable({
@@ -1,4 +1,5 @@
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 { RecordTableBodyDragDropContextProvider } from '@/object-record/record-table/record-table-body/components/RecordTableBodyDragDropContextProvider';
import { RecordTableBodyDroppable } from '@/object-record/record-table/record-table-body/components/RecordTableBodyDroppable';
@@ -21,11 +22,13 @@ export const RecordTableNoRecordGroupBody = () => {
}
return (
<RecordTableNoRecordGroupBodyContextProvider>
<RecordTableBodyDragDropContextProvider>
<RecordTableBodyDroppable>
<RecordTablePendingRow />
<RecordTableNoRecordGroupRows />
</RecordTableBodyDroppable>
</RecordTableBodyDragDropContextProvider>
</RecordTableNoRecordGroupBodyContextProvider>
);
};
@@ -1,4 +1,4 @@
import { useContext, useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
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 { useLazyLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLazyLoadRecordIndexTable';
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 { tableEncounteredUnrecoverableErrorComponentState } from '@/object-record/record-table/states/tableEncounteredUnrecoverableErrorComponentState';
import { tableLastRowVisibleComponentState } from '@/object-record/record-table/states/tableLastRowVisibleComponentState';
@@ -18,7 +18,7 @@ import { isNonEmptyString, isNull } from '@sniptt/guards';
import { useScrollToPosition } from '~/hooks/useScrollToPosition';
export const RecordTableNoRecordGroupBodyEffect = () => {
const { objectNameSingular } = useContext(RecordTableContext);
const { objectNameSingular } = useRecordTableContextOrThrow();
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
@@ -1,4 +1,4 @@
import { useContext, useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
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 { recordIndexHasFetchedAllRecordsByGroupComponentState } from '@/object-record/record-index/states/recordIndexHasFetchedAllRecordsByGroupComponentState';
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 { isNonEmptyString, isNull } from '@sniptt/guards';
import { useScrollToPosition } from '~/hooks/useScrollToPosition';
export const RecordTableRecordGroupBodyEffect = () => {
const { objectNameSingular } = useContext(RecordTableContext);
const { objectNameSingular } = useRecordTableContextOrThrow();
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
@@ -1,13 +1,13 @@
import { RecordGroupContext } from '@/object-record/record-group/states/context/RecordGroupContext';
import { visibleRecordGroupIdsComponentSelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentSelector';
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 { RecordTableBodyDroppable } from '@/object-record/record-table/record-table-body/components/RecordTableBodyDroppable';
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 { 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 { RecordTableRecordGroupSectionLoadMore } from '@/object-record/record-table/record-table-section/components/RecordTableRecordGroupSectionLoadMore';
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
@@ -30,20 +30,19 @@ export const RecordTableRecordGroupsBody = () => {
return (
<RecordTableBodyRecordGroupDragDropContextProvider>
<RecordTableBodyDroppable isDropDisabled>
<RecordTablePendingRow />
</RecordTableBodyDroppable>
{visibleRecordGroupIds.map((recordGroupId) => (
<RecordGroupContext.Provider
{visibleRecordGroupIds.map((recordGroupId, index) => (
<RecordTableRecordGroupBodyContextProvider
key={recordGroupId}
value={{ recordGroupId }}
recordGroupId={recordGroupId}
>
{index > 0 && <RecordTableRecordGroupEmptyRow />}
<RecordGroupContext.Provider value={{ recordGroupId }}>
<RecordTableBodyDroppable recordGroupId={recordGroupId}>
<RecordTableRecordGroupSection />
<RecordTableRecordGroupRows />
<RecordTableRecordGroupSectionLoadMore />
</RecordTableBodyDroppable>
</RecordGroupContext.Provider>
</RecordTableRecordGroupBodyContextProvider>
))}
</RecordTableBodyRecordGroupDragDropContextProvider>
);
@@ -5,8 +5,8 @@ import { BORDER_COMMON, ThemeContext } from 'twenty-ui';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { useFieldFocus } from '@/object-record/record-field/hooks/useFieldFocus';
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 { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
import {
DEFAULT_CELL_SCOPE,
useOpenRecordTableCellFromCell,
@@ -47,7 +47,7 @@ export const RecordTableCellBaseContainer = ({
const { hasSoftFocus, cellPosition } = useContext(RecordTableCellContext);
const { onMoveSoftFocusToCell, onCellMouseEnter } =
useContext(RecordTableContext);
useRecordTableBodyContextOrThrow();
const handleContainerMouseMove = () => {
setIsFocused(true);
@@ -1,5 +1,5 @@
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 { RecordTableCellDisplayContainer } from './RecordTableCellDisplayContainer';
@@ -7,9 +7,10 @@ export const RecordTableCellDisplayMode = ({
children,
softFocus,
}: React.PropsWithChildren<{ softFocus?: boolean }>) => {
const { onActionMenuDropdownOpened } = useContext(RecordTableContext);
const { recordId } = useContext(FieldContext);
const { onActionMenuDropdownOpened } = useRecordTableBodyContextOrThrow();
const handleActionMenuDropdown = (event: React.MouseEvent) => {
onActionMenuDropdownOpened(event, recordId);
};
@@ -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 { RecordUpdateContext } from '@/object-record/record-table/contexts/EntityUpdateMutationHookContext';
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 { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
@@ -18,8 +18,10 @@ export const RecordTableCellFieldContextWrapper = ({
}: {
children: ReactNode;
}) => {
const { objectMetadataItem } = useContext(RecordTableContext);
const { objectMetadataItem } = useRecordTableContextOrThrow();
const { columnDefinition } = useContext(RecordTableCellContext);
const { recordId, pathToShowPage } = useContext(RecordTableRowContext);
const updateRecord = useContext(RecordUpdateContext);
@@ -4,17 +4,18 @@ import { FieldInput } from '@/object-record/record-field/components/FieldInput';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
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 { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId';
import { activeDropdownFocusIdState } from '@/ui/layout/dropdown/states/activeDropdownFocusIdState';
import { useRecoilCallback } from 'recoil';
export const RecordTableCellFieldInput = () => {
const { onUpsertRecord, onMoveFocus, onCloseTableCell } =
useContext(RecordTableContext);
const { recordId, fieldDefinition } = useContext(FieldContext);
const { onUpsertRecord, onMoveFocus, onCloseTableCell } =
useRecordTableBodyContextOrThrow();
const isFieldReadOnly = useIsFieldValueReadOnly();
const handleEnter: FieldInputEvent = (persistField) => {
@@ -3,19 +3,25 @@ import { useContext } from 'react';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
import { css } from '@emotion/react';
import { IconListViewGrip } from 'twenty-ui';
const StyledContainer = styled.div`
const StyledContainer = styled.div<{ isPendingRow?: boolean }>`
border-color: transparent;
cursor: grab;
width: 16px;
height: 32px;
z-index: 200;
display: flex;
height: 32px;
width: 16px;
${({ isPendingRow }) =>
!isPendingRow
? css`
&:hover .icon {
opacity: 1;
}
`
: ''};
border-color: transparent;
z-index: 200;
`;
const StyledIconWrapper = styled.div<{ isDragging: boolean }>`
@@ -24,7 +30,9 @@ const StyledIconWrapper = styled.div<{ isDragging: boolean }>`
`;
export const RecordTableCellGrip = () => {
const { dragHandleProps, isDragging } = useContext(RecordTableRowContext);
const { dragHandleProps, isDragging, isPendingRow } = useContext(
RecordTableRowContext,
);
return (
<RecordTableTd
@@ -34,7 +42,7 @@ export const RecordTableCellGrip = () => {
hasRightBorder={false}
hasBottomBorder={false}
>
<StyledContainer>
<StyledContainer isPendingRow={isPendingRow}>
<StyledIconWrapper className="icon" isDragging={isDragging}>
<IconListViewGrip />
</StyledIconWrapper>
@@ -21,7 +21,7 @@ import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
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';
type RecordTableCellSoftFocusModeProps = {
@@ -36,6 +36,8 @@ export const RecordTableCellSoftFocusMode = ({
const { columnIndex } = useContext(RecordTableCellContext);
const { recordId } = useContext(FieldContext);
const { onActionMenuDropdownOpened } = useRecordTableBodyContextOrThrow();
const isFieldReadOnly = useIsFieldValueReadOnly();
const { openTableCell } = useOpenRecordTableCellFromCell();
@@ -135,8 +137,6 @@ export const RecordTableCellSoftFocusMode = ({
*/
};
const { onActionMenuDropdownOpened } = useContext(RecordTableContext);
const handleActionMenuDropdown = (event: React.MouseEvent) => {
onActionMenuDropdownOpened(event, recordId);
};
@@ -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');
});
});
@@ -1,22 +1,26 @@
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 { 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 { 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();
@@ -28,10 +32,19 @@ const onColumnsChange = jest.fn();
const recordTableId = 'scopeId';
const Wrapper = ({ children }: { children: React.ReactNode }) => (
<RecoilRoot>
<RecoilRoot
initializeState={(snapshot) => {
snapshot.set(objectMetadataItemsState, generatedMockObjectMetadataItems);
}}
>
<RecordTableComponentInstance
recordTableId={recordTableId}
onColumnsChange={onColumnsChange}
>
<RecordTableContextProvider
recordTableId={recordTableId}
viewBarId="viewBarId"
objectNameSingular={CoreObjectNameSingular.Person}
>
<FieldContext.Provider
value={{
@@ -49,11 +62,12 @@ const Wrapper = ({ children }: { children: React.ReactNode }) => (
</RecordTableCellContext.Provider>
</RecordTableRowContext.Provider>
</FieldContext.Provider>
</RecordTableContextProvider>
</RecordTableComponentInstance>
</RecoilRoot>
);
describe('useCloseRecordTableCell', () => {
describe('useCloseRecordTableCellNoGroup', () => {
it('should work as expected', async () => {
const { result } = renderHook(
() => {
@@ -65,7 +79,7 @@ describe('useCloseRecordTableCell', () => {
currentTableCellInEditModePosition,
);
return {
...useCloseRecordTableCell(),
...useCloseRecordTableCellNoGroup(),
...useDragSelect(),
isTableCellInEditMode,
};
@@ -76,7 +90,7 @@ describe('useCloseRecordTableCell', () => {
);
act(() => {
result.current.closeTableCell();
result.current.closeTableCellNoGroup();
});
expect(result.current.isDragSelectionStartEnabled()).toBe(true);
@@ -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,
};
};
@@ -5,13 +5,18 @@ 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 { 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 { useCloseCurrentTableCellInEditMode } from '../../hooks/internal/useCloseCurrentTableCellInEditMode';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { useCallback } from 'react';
export const useCloseRecordTableCellNoGroup = () => {
const { recordTableId } = useRecordTableContextOrThrow();
export const useCloseRecordTableCellV2 = (recordTableId: string) => {
const setHotkeyScope = useSetHotkeyScope();
const { setDragSelectionStartEnabled } = useDragSelect();
const { toggleClickOutsideListener } = useClickOutsideListener(
@@ -25,18 +30,25 @@ export const useCloseRecordTableCellV2 = (recordTableId: string) => {
recordTablePendingRecordIdComponentState,
recordTableId,
);
const resetRecordTablePendingRecordId =
useResetRecoilState(pendingRecordIdState);
const closeTableCell = async () => {
const closeTableCellNoGroup = useCallback(() => {
toggleClickOutsideListener(true);
setDragSelectionStartEnabled(true);
closeCurrentTableCellInEditMode();
setHotkeyScope(TableHotkeyScope.TableSoftFocus);
resetRecordTablePendingRecordId();
};
}, [
closeCurrentTableCellInEditMode,
resetRecordTablePendingRecordId,
setDragSelectionStartEnabled,
setHotkeyScope,
toggleClickOutsideListener,
]);
return {
closeTableCell,
closeTableCellNoGroup,
};
};
@@ -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,
};
};
@@ -5,12 +5,12 @@ import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useI
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
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 { useCurrentTableCellPosition } from '@/object-record/record-table/record-table-cell/hooks/useCurrentCellPosition';
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useRecordTableBodyContextOrThrow } from '@/object-record/record-table/contexts/RecordTableBodyContext';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
export const DEFAULT_CELL_SCOPE: HotkeyScope = {
@@ -28,13 +28,16 @@ export type OpenTableCellArgs = {
};
export const useOpenRecordTableCellFromCell = () => {
const { onOpenTableCell } = useContext(RecordTableContext);
const cellPosition = useCurrentTableCellPosition();
const customCellHotkeyScope = useContext(CellHotkeyScopeContext);
const { recordId, fieldDefinition } = useContext(FieldContext);
const { pathToShowPage, objectNameSingular } = useContext(
RecordTableRowContext,
);
const { onOpenTableCell } = useRecordTableBodyContextOrThrow();
const cellPosition = useCurrentTableCellPosition();
const isFieldReadOnly = useIsFieldValueReadOnly();
const openTableCell = (
@@ -20,12 +20,11 @@ import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useC
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
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 { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropdownFocusIdForRecordField';
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
import { useClickOustideListenerStates } from '@/ui/utilities/pointer-event/hooks/useClickOustideListenerStates';
import { useContext } from 'react';
import { useNavigate } from 'react-router-dom';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
@@ -49,7 +48,7 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
const { getClickOutsideListenerIsActivatedState } =
useClickOustideListenerStates(RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID);
const { indexIdentifierUrl } = useContext(RecordIndexRootPropsContext);
const { indexIdentifierUrl } = useRecordIndexContextOrThrow();
const moveEditModeToTableCellPosition =
useMoveEditModeToTableCellPosition(tableScopeId);
@@ -1,12 +1,11 @@
import styled from '@emotion/styled';
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 { RecordTableHeaderCheckboxColumn } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderCheckboxColumn';
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 { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
const StyledTableHead = styled.thead`
cursor: pointer;
@@ -77,14 +76,8 @@ const StyledTableHead = styled.thead`
}
`;
export const RecordTableHeader = ({
objectNameSingular,
}: {
objectNameSingular: string;
}) => {
const visibleTableColumns = useRecoilComponentValueV2(
visibleTableColumnsComponentSelector,
);
export const RecordTableHeader = () => {
const { visibleTableColumns } = useRecordTableContextOrThrow();
return (
<StyledTableHead id="record-table-header" data-select-disable>
@@ -92,11 +85,7 @@ export const RecordTableHeader = ({
<RecordTableHeaderDragDropColumn />
<RecordTableHeaderCheckboxColumn />
{visibleTableColumns.map((column) => (
<RecordTableHeaderCell
key={column.fieldMetadataId}
column={column}
objectNameSingular={objectNameSingular}
/>
<RecordTableHeaderCell key={column.fieldMetadataId} column={column} />
))}
<RecordTableHeaderLastColumn />
</tr>
@@ -3,9 +3,9 @@ import { useCallback, useMemo, useState } from 'react';
import { useRecoilCallback } from 'recoil';
import { IconPlus, LightIconButton } from 'twenty-ui';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { isObjectMetadataReadOnly } from '@/object-metadata/utils/isObjectMetadataReadOnly';
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 { useTableColumns } from '@/object-record/record-table/hooks/useTableColumns';
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)};
`;
type RecordTableHeaderCellProps = {
column: ColumnDefinition<FieldMetadata>;
};
export const RecordTableHeaderCell = ({
column,
objectNameSingular,
}: {
column: ColumnDefinition<FieldMetadata>;
objectNameSingular: string;
}) => {
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
}: RecordTableHeaderCellProps) => {
const { recordTableId, objectMetadataItem } = useRecordTableContextOrThrow();
const resizeFieldOffsetState = useRecoilComponentCallbackStateV2(
resizeFieldOffsetComponentState,
@@ -199,7 +197,7 @@ export const RecordTableHeaderCell = ({
const disableColumnResize =
column.isLabelIdentifier && isMobile && !isRecordTableScrolledLeft;
const { createNewTableRecord } = useCreateNewTableRecord();
const { createNewTableRecord } = useCreateNewTableRecord(recordTableId);
const handlePlusButtonClick = () => {
createNewTableRecord();
@@ -1,11 +1,11 @@
import { useCallback, useContext } from 'react';
import { useCallback } from 'react';
import { useLocation } from 'react-router-dom';
import { useSetRecoilState } from 'recoil';
import { IconSettings, MenuItem, UndecoratedLink, useIcons } from 'twenty-ui';
import { getObjectSlug } from '@/object-metadata/utils/getObjectSlug';
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 { hiddenTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/hiddenTableColumnsComponentSelector';
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';
export const RecordTableHeaderPlusButtonContent = () => {
const { objectMetadataItem } = useContext(RecordTableContext);
const { objectMetadataItem } = useRecordTableContextOrThrow();
const { closeDropdown } = useDropdown();
const hiddenTableColumns = useRecoilComponentValueV2(
@@ -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
/>
);
};
@@ -4,8 +4,8 @@ import { ReactNode, useContext, useEffect, useRef } from 'react';
import { useInView } from 'react-intersection-observer';
import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage';
import { RecordIndexRootPropsContext } from '@/object-record/record-index/contexts/RecordIndexRootPropsContext';
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { RecordTableTr } from '@/object-record/record-table/record-table-row/components/RecordTableTr';
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
@@ -31,8 +31,8 @@ export const RecordTableRowWrapper = ({
}: RecordTableRowWrapperProps) => {
const trRef = useRef<HTMLTableRowElement>(null);
const { objectMetadataItem } = useContext(RecordTableContext);
const { onIndexRecordsLoaded } = useContext(RecordIndexRootPropsContext);
const { objectMetadataItem } = useRecordTableContextOrThrow();
const { onIndexRecordsLoaded } = useRecordIndexContextOrThrow();
const theme = useTheme();
@@ -78,7 +78,12 @@ export const RecordTableRowWrapper = ({
}, [inView, onIndexRecordsLoaded]);
return (
<Draggable key={recordId} draggableId={recordId} index={rowIndexForDrag}>
<Draggable
key={recordId}
draggableId={recordId}
index={rowIndexForDrag}
isDragDisabled={isPendingRow}
>
{(draggableProvided, draggableSnapshot) => (
<RecordTableTr
ref={(node) => {
@@ -0,0 +1,7 @@
import styled from '@emotion/styled';
const StyledTrContainer = styled.tr`
height: 32px;
`;
export const RecordTableRecordGroupEmptyRow = StyledTrContainer;
@@ -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}
/>
);
};
@@ -1,14 +1,13 @@
import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
import { useLazyLoadRecordIndexTable } from '@/object-record/record-index/hooks/useLazyLoadRecordIndexTable';
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 { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { useContext } from 'react';
import { IconArrowDown } from 'twenty-ui';
export const RecordTableRecordGroupSectionLoadMore = () => {
const { objectNameSingular } = useContext(RecordTableContext);
const { objectNameSingular } = useRecordTableContextOrThrow();
const currentRecordGroupId = useCurrentRecordGroupId();
@@ -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,
});
@@ -4,7 +4,7 @@ import { createComponentStateV2 } from '@/ui/utilities/state/component-state/uti
export const recordTablePendingRecordIdComponentState = createComponentStateV2<
string | null
>({
key: 'recordTablePendingRecordIdState',
key: 'recordTablePendingRecordIdComponentState',
defaultValue: null,
componentInstanceContext: RecordTableComponentInstanceContext,
});
@@ -3,7 +3,7 @@ import styled from '@emotion/styled';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
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 { SignInBackgroundMockContainerEffect } from '@/sign-in-background-mock/components/SignInBackgroundMockContainerEffect';
import { ViewBar } from '@/views/components/ViewBar';
@@ -28,7 +28,7 @@ export const SignInBackgroundMockContainer = () => {
return (
<StyledContainer>
<RecordIndexRootPropsContext.Provider
<RecordIndexContextProvider
value={{
recordIndexId,
objectNamePlural,
@@ -36,7 +36,6 @@ export const SignInBackgroundMockContainer = () => {
objectMetadataItem,
onIndexRecordsLoaded: () => {},
indexIdentifierUrl: () => '',
onCreateRecord: () => {},
}}
>
<ViewComponentInstanceContext.Provider
@@ -69,7 +68,7 @@ export const SignInBackgroundMockContainer = () => {
</ActionMenuComponentInstanceContext.Provider>
</ContextStoreComponentInstanceContext.Provider>
</ViewComponentInstanceContext.Provider>
</RecordIndexRootPropsContext.Provider>
</RecordIndexContextProvider>
</StyledContainer>
);
};
@@ -1,6 +1,6 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
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 { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
import { usePersistViewFieldRecords } from '@/views/hooks/internal/usePersistViewFieldRecords';
@@ -18,7 +18,6 @@ import { GraphQLView } from '@/views/types/GraphQLView';
import { View } from '@/views/types/View';
import { ViewGroup } from '@/views/types/ViewGroup';
import { ViewType } from '@/views/types/ViewType';
import { useContext } from 'react';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-ui';
import { v4 } from 'uuid';
@@ -57,7 +56,7 @@ export const useCreateViewFromCurrentView = (viewBarComponentId?: string) => {
const { createViewFilterGroupRecords } = usePersistViewFilterGroupRecords();
const { objectMetadataItem } = useContext(RecordIndexRootPropsContext);
const { objectMetadataItem } = useRecordIndexContextOrThrow();
const createViewFromCurrentView = useRecoilCallback(
({ snapshot, set }) =>
@@ -12,9 +12,8 @@ import { RecordIndexContainer } from '@/object-record/record-index/components/Re
import { RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect } from '@/object-record/record-index/components/RecordIndexContainerContextStoreNumberOfSelectedRecordsEffect';
import { RecordIndexContainerContextStoreObjectMetadataEffect } from '@/object-record/record-index/components/RecordIndexContainerContextStoreObjectMetadataEffect';
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 { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords';
import { PageBody } from '@/ui/layout/page/components/PageBody';
import { PageContainer } from '@/ui/layout/page/components/PageContainer';
import { PageTitle } from '@/ui/utilities/page-title/components/PageTitle';
@@ -41,12 +40,6 @@ export const RecordIndexPage = () => {
objectNameSingular,
});
const { createNewTableRecord } = useCreateNewTableRecord(recordIndexId);
const handleCreateRecord = () => {
createNewTableRecord();
};
const { indexIdentifierUrl } = useHandleIndexIdentifierClick({
objectMetadataItem,
recordIndexId,
@@ -63,7 +56,7 @@ export const RecordIndexPage = () => {
return (
<PageContainer>
<RecordIndexRootPropsContext.Provider
<RecordIndexContextProvider
value={{
recordIndexId,
objectNamePlural,
@@ -71,7 +64,6 @@ export const RecordIndexPage = () => {
objectMetadataItem,
onIndexRecordsLoaded: handleIndexRecordsLoaded,
indexIdentifierUrl,
onCreateRecord: handleCreateRecord,
}}
>
<ViewComponentInstanceContext.Provider
@@ -100,7 +92,7 @@ export const RecordIndexPage = () => {
</ActionMenuComponentInstanceContext.Provider>
</ContextStoreComponentInstanceContext.Provider>
</ViewComponentInstanceContext.Provider>
</RecordIndexRootPropsContext.Provider>
</RecordIndexContextProvider>
</PageContainer>
);
};
@@ -2,7 +2,8 @@ import { Decorator } from '@storybook/react';
import { useRecoilValue } from 'recoil';
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';
export const RecordTableDecorator: Decorator = (Story) => {
@@ -17,10 +18,17 @@ export const RecordTableDecorator: Decorator = (Story) => {
}
return (
<RecordTableContext.Provider
<RecordTableContextProvider
value={{
objectNameSingular: personObjectMetadataItem?.nameSingular,
objectNameSingular: personObjectMetadataItem.nameSingular,
objectMetadataItem: personObjectMetadataItem,
recordTableId: 'persons',
viewBarId: 'view-bar',
visibleTableColumns: [],
}}
>
<RecordTableBodyContextProvider
value={{
onCellMouseEnter: () => {},
onCloseTableCell: () => {},
onOpenTableCell: () => {},
@@ -28,12 +36,10 @@ export const RecordTableDecorator: Decorator = (Story) => {
onMoveFocus: () => {},
onMoveSoftFocusToCell: () => {},
onUpsertRecord: () => {},
recordTableId: 'persons',
viewBarId: 'view-bar',
visibleTableColumns: [],
}}
>
<Story />
</RecordTableContext.Provider>
</RecordTableBodyContextProvider>
</RecordTableContextProvider>
);
};
@@ -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;
};
@@ -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>);