Fix aggregate bar (#11620)
Closes https://github.com/twentyhq/twenty/issues/10943 Also adds stories: <img width="1512" alt="image" src="https://github.com/user-attachments/assets/377059b1-f6b5-4d8c-b7d1-e74e70448445" />
This commit is contained in:
@ -23,8 +23,8 @@ import { ViewField } from '@/views/types/ViewField';
|
|||||||
import { mapViewFieldsToColumnDefinitions } from '@/views/utils/mapViewFieldsToColumnDefinitions';
|
import { mapViewFieldsToColumnDefinitions } from '@/views/utils/mapViewFieldsToColumnDefinitions';
|
||||||
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
|
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
|
||||||
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
||||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||||
|
|
||||||
export const useLoadRecordIndexStates = () => {
|
export const useLoadRecordIndexStates = () => {
|
||||||
const setContextStoreTargetedRecordsRuleComponentState =
|
const setContextStoreTargetedRecordsRuleComponentState =
|
||||||
|
|||||||
@ -0,0 +1,117 @@
|
|||||||
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
|
import { RecordTableWithWrappers } from '@/object-record/record-table/components/RecordTableWithWrappers';
|
||||||
|
import { RecordTableEmptyStateNoGroupNoRecordAtAll } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoGroupNoRecordAtAll';
|
||||||
|
import { fireEvent, userEvent, within } from '@storybook/test';
|
||||||
|
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||||
|
import { ContextStoreDecorator } from '~/testing/decorators/ContextStoreDecorator';
|
||||||
|
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||||
|
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||||
|
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
||||||
|
import { RecordTableDecorator } from '~/testing/decorators/RecordTableDecorator';
|
||||||
|
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||||
|
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||||
|
import { mockedViewsData } from '~/testing/mock-data/views';
|
||||||
|
import { sleep } from '~/utils/sleep';
|
||||||
|
|
||||||
|
const meta: Meta = {
|
||||||
|
title: 'Modules/ObjectRecord/RecordTable/RecordTable',
|
||||||
|
component: RecordTableWithWrappers,
|
||||||
|
decorators: [
|
||||||
|
ComponentDecorator,
|
||||||
|
MemoryRouterDecorator,
|
||||||
|
RecordTableDecorator,
|
||||||
|
ContextStoreDecorator,
|
||||||
|
SnackBarDecorator,
|
||||||
|
ObjectMetadataItemsDecorator,
|
||||||
|
I18nFrontDecorator,
|
||||||
|
],
|
||||||
|
args: {
|
||||||
|
recordTableId: `companies-${mockedViewsData[0].id}`,
|
||||||
|
viewBarId: 'view-bar',
|
||||||
|
objectNameSingular: 'company',
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
recordTableObjectNameSingular: 'company',
|
||||||
|
msw: graphqlMocks,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof RecordTableEmptyStateNoGroupNoRecordAtAll>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
play: async ({ canvasElement }) => {
|
||||||
|
const canvas = within(canvasElement);
|
||||||
|
|
||||||
|
await canvas.findByText('Linkedin');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const HeaderMenuOpen: Story = {
|
||||||
|
play: async () => {
|
||||||
|
const canvas = within(document.body);
|
||||||
|
await canvas.findByText('Linkedin');
|
||||||
|
|
||||||
|
const headerMenuButton = await canvas.findByText('Domain Name');
|
||||||
|
|
||||||
|
await userEvent.click(headerMenuButton);
|
||||||
|
|
||||||
|
await canvas.findByText('Move right');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ScrolledLeft: Story = {
|
||||||
|
play: async () => {
|
||||||
|
const canvas = within(document.body);
|
||||||
|
await canvas.findByText('Linkedin');
|
||||||
|
|
||||||
|
const scrollWrapper = document.body.querySelector(
|
||||||
|
'.scroll-wrapper-x-enabled',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!scrollWrapper) {
|
||||||
|
throw new Error('Scroll wrapper not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
await sleep(1000);
|
||||||
|
|
||||||
|
fireEvent.scroll(scrollWrapper, {
|
||||||
|
target: {
|
||||||
|
scrollLeft: 100,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await canvas.findByText('Facebook');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ScrolledBottom: Story = {
|
||||||
|
parameters: {
|
||||||
|
container: {
|
||||||
|
height: 300,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
play: async () => {
|
||||||
|
const canvas = within(document.body);
|
||||||
|
await canvas.findByText('Linkedin');
|
||||||
|
|
||||||
|
const scrollWrapper = document.body.querySelector(
|
||||||
|
'.scroll-wrapper-y-enabled',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!scrollWrapper) {
|
||||||
|
throw new Error('Scroll wrapper not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
await sleep(1000);
|
||||||
|
|
||||||
|
fireEvent.scroll(scrollWrapper, {
|
||||||
|
target: {
|
||||||
|
scrollTop: 80,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await canvas.findByText('Facebook');
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -64,7 +64,7 @@ export const RecordTable = () => {
|
|||||||
tableBodyRef={tableBodyRef}
|
tableBodyRef={tableBodyRef}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{recordTableIsEmpty ? (
|
{recordTableIsEmpty && !hasRecordGroups ? (
|
||||||
<RecordTableEmpty
|
<RecordTableEmpty
|
||||||
tableBodyRef={tableBodyRef}
|
tableBodyRef={tableBodyRef}
|
||||||
hasRecordGroups={hasRecordGroups}
|
hasRecordGroups={hasRecordGroups}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { StyledTable } from '@/object-record/record-table/components/RecordTableStyles';
|
import { StyledTable } from '@/object-record/record-table/components/RecordTableStyles';
|
||||||
import { RecordTableEmptyState } from '@/object-record/record-table/empty-state/components/RecordTableEmptyState';
|
import { RecordTableEmptyState } from '@/object-record/record-table/empty-state/components/RecordTableEmptyState';
|
||||||
import { RecordTableRecordGroupsBody } from '@/object-record/record-table/record-table-body/components/RecordTableRecordGroupsBody';
|
|
||||||
import { RecordTableHeader } from '@/object-record/record-table/record-table-header/components/RecordTableHeader';
|
import { RecordTableHeader } from '@/object-record/record-table/record-table-header/components/RecordTableHeader';
|
||||||
|
|
||||||
export interface RecordTableEmptyProps {
|
export interface RecordTableEmptyProps {
|
||||||
@ -8,18 +7,11 @@ export interface RecordTableEmptyProps {
|
|||||||
hasRecordGroups: boolean;
|
hasRecordGroups: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RecordTableEmpty = ({
|
export const RecordTableEmpty = ({ tableBodyRef }: RecordTableEmptyProps) => (
|
||||||
tableBodyRef,
|
|
||||||
hasRecordGroups,
|
|
||||||
}: RecordTableEmptyProps) => (
|
|
||||||
<>
|
<>
|
||||||
<StyledTable ref={tableBodyRef}>
|
<StyledTable ref={tableBodyRef}>
|
||||||
<RecordTableHeader />
|
<RecordTableHeader />
|
||||||
</StyledTable>
|
</StyledTable>
|
||||||
{hasRecordGroups ? (
|
<RecordTableEmptyState />
|
||||||
<RecordTableRecordGroupsBody />
|
|
||||||
) : (
|
|
||||||
<RecordTableEmptyState />
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,35 +1,40 @@
|
|||||||
import { Meta, StoryObj } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
|
import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider';
|
||||||
import { RecordTableEmptyStateNoGroupNoRecordAtAll } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoGroupNoRecordAtAll';
|
import { RecordTableEmptyStateNoGroupNoRecordAtAll } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoGroupNoRecordAtAll';
|
||||||
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
|
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||||
|
import { ContextStoreDecorator } from '~/testing/decorators/ContextStoreDecorator';
|
||||||
|
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||||
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
||||||
import { RecordTableDecorator } from '~/testing/decorators/RecordTableDecorator';
|
import { RecordTableDecorator } from '~/testing/decorators/RecordTableDecorator';
|
||||||
|
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
|
||||||
|
|
||||||
const meta: Meta = {
|
const meta: Meta = {
|
||||||
title:
|
title:
|
||||||
'Modules/ObjectRecord/RecordTable/RecordTableEmptyStateNoGroupNoRecordAtAll',
|
'Modules/ObjectRecord/RecordTable/RecordTableEmptyStateNoGroupNoRecordAtAll',
|
||||||
component: RecordTableEmptyStateNoGroupNoRecordAtAll,
|
component: RecordTableEmptyStateNoGroupNoRecordAtAll,
|
||||||
decorators: [
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<RecordTableContextProvider
|
||||||
|
recordTableId="persons"
|
||||||
|
viewBarId="view-bar"
|
||||||
|
objectNameSingular="person"
|
||||||
|
>
|
||||||
|
<Story />
|
||||||
|
</RecordTableContextProvider>
|
||||||
|
),
|
||||||
ComponentDecorator,
|
ComponentDecorator,
|
||||||
MemoryRouterDecorator,
|
MemoryRouterDecorator,
|
||||||
ObjectMetadataItemsDecorator,
|
|
||||||
RecordTableDecorator,
|
RecordTableDecorator,
|
||||||
(Story) => (
|
ContextStoreDecorator,
|
||||||
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
|
SnackBarDecorator,
|
||||||
<RecordTableComponentInstance
|
ObjectMetadataItemsDecorator,
|
||||||
recordTableId="persons"
|
I18nFrontDecorator,
|
||||||
onColumnsChange={() => {}}
|
|
||||||
>
|
|
||||||
<Story />
|
|
||||||
</RecordTableComponentInstance>
|
|
||||||
</SnackBarProviderScope>
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
parameters: {
|
parameters: {
|
||||||
|
recordTableObjectNameSingular: 'person',
|
||||||
msw: graphqlMocks,
|
msw: graphqlMocks,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,35 +1,40 @@
|
|||||||
import { Meta, StoryObj } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
|
import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider';
|
||||||
import { RecordTableEmptyStateNoRecordFoundForFilter } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordFoundForFilter';
|
import { RecordTableEmptyStateNoRecordFoundForFilter } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateNoRecordFoundForFilter';
|
||||||
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
|
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||||
|
import { ContextStoreDecorator } from '~/testing/decorators/ContextStoreDecorator';
|
||||||
|
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||||
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
||||||
import { RecordTableDecorator } from '~/testing/decorators/RecordTableDecorator';
|
import { RecordTableDecorator } from '~/testing/decorators/RecordTableDecorator';
|
||||||
|
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
|
||||||
|
|
||||||
const meta: Meta = {
|
const meta: Meta = {
|
||||||
title:
|
title:
|
||||||
'Modules/ObjectRecord/RecordTable/RecordTableEmptyStateNoRecordFoundForFilter',
|
'Modules/ObjectRecord/RecordTable/RecordTableEmptyStateNoRecordFoundForFilter',
|
||||||
component: RecordTableEmptyStateNoRecordFoundForFilter,
|
component: RecordTableEmptyStateNoRecordFoundForFilter,
|
||||||
decorators: [
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<RecordTableContextProvider
|
||||||
|
recordTableId="persons"
|
||||||
|
viewBarId="view-bar"
|
||||||
|
objectNameSingular="person"
|
||||||
|
>
|
||||||
|
<Story />
|
||||||
|
</RecordTableContextProvider>
|
||||||
|
),
|
||||||
ComponentDecorator,
|
ComponentDecorator,
|
||||||
MemoryRouterDecorator,
|
MemoryRouterDecorator,
|
||||||
ObjectMetadataItemsDecorator,
|
|
||||||
RecordTableDecorator,
|
RecordTableDecorator,
|
||||||
(Story) => (
|
ContextStoreDecorator,
|
||||||
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
|
SnackBarDecorator,
|
||||||
<RecordTableComponentInstance
|
ObjectMetadataItemsDecorator,
|
||||||
recordTableId="persons"
|
I18nFrontDecorator,
|
||||||
onColumnsChange={() => {}}
|
|
||||||
>
|
|
||||||
<Story />
|
|
||||||
</RecordTableComponentInstance>
|
|
||||||
</SnackBarProviderScope>
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
parameters: {
|
parameters: {
|
||||||
|
recordTableObjectNameSingular: 'person',
|
||||||
msw: graphqlMocks,
|
msw: graphqlMocks,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,34 +1,39 @@
|
|||||||
import { Meta, StoryObj } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
|
import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider';
|
||||||
import { RecordTableEmptyStateRemote } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateRemote';
|
import { RecordTableEmptyStateRemote } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateRemote';
|
||||||
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
|
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||||
|
import { ContextStoreDecorator } from '~/testing/decorators/ContextStoreDecorator';
|
||||||
|
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||||
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
||||||
import { RecordTableDecorator } from '~/testing/decorators/RecordTableDecorator';
|
import { RecordTableDecorator } from '~/testing/decorators/RecordTableDecorator';
|
||||||
|
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
|
||||||
|
|
||||||
const meta: Meta = {
|
const meta: Meta = {
|
||||||
title: 'Modules/ObjectRecord/RecordTable/RecordTableEmptyStateRemote',
|
title: 'Modules/ObjectRecord/RecordTable/RecordTableEmptyStateRemote',
|
||||||
component: RecordTableEmptyStateRemote,
|
component: RecordTableEmptyStateRemote,
|
||||||
decorators: [
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<RecordTableContextProvider
|
||||||
|
recordTableId="persons"
|
||||||
|
viewBarId="view-bar"
|
||||||
|
objectNameSingular="person"
|
||||||
|
>
|
||||||
|
<Story />
|
||||||
|
</RecordTableContextProvider>
|
||||||
|
),
|
||||||
ComponentDecorator,
|
ComponentDecorator,
|
||||||
MemoryRouterDecorator,
|
MemoryRouterDecorator,
|
||||||
ObjectMetadataItemsDecorator,
|
|
||||||
RecordTableDecorator,
|
RecordTableDecorator,
|
||||||
(Story) => (
|
ContextStoreDecorator,
|
||||||
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
|
SnackBarDecorator,
|
||||||
<RecordTableComponentInstance
|
ObjectMetadataItemsDecorator,
|
||||||
recordTableId="persons"
|
I18nFrontDecorator,
|
||||||
onColumnsChange={() => {}}
|
|
||||||
>
|
|
||||||
<Story />
|
|
||||||
</RecordTableComponentInstance>
|
|
||||||
</SnackBarProviderScope>
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
parameters: {
|
parameters: {
|
||||||
|
recordTableObjectNameSingular: 'person',
|
||||||
msw: graphqlMocks,
|
msw: graphqlMocks,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,39 +1,39 @@
|
|||||||
import { Meta, StoryObj } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
|
import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider';
|
||||||
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
|
|
||||||
import { RecordTableEmptyStateSoftDelete } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete';
|
import { RecordTableEmptyStateSoftDelete } from '@/object-record/record-table/empty-state/components/RecordTableEmptyStateSoftDelete';
|
||||||
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
|
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||||
|
import { ContextStoreDecorator } from '~/testing/decorators/ContextStoreDecorator';
|
||||||
|
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||||
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
||||||
import { RecordTableDecorator } from '~/testing/decorators/RecordTableDecorator';
|
import { RecordTableDecorator } from '~/testing/decorators/RecordTableDecorator';
|
||||||
|
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
|
||||||
|
|
||||||
const meta: Meta = {
|
const meta: Meta = {
|
||||||
title: 'Modules/ObjectRecord/RecordTable/RecordTableEmptyStateSoftDelete',
|
title: 'Modules/ObjectRecord/RecordTable/RecordTableEmptyStateSoftDelete',
|
||||||
component: RecordTableEmptyStateSoftDelete,
|
component: RecordTableEmptyStateSoftDelete,
|
||||||
decorators: [
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<RecordTableContextProvider
|
||||||
|
recordTableId="persons"
|
||||||
|
viewBarId="view-bar"
|
||||||
|
objectNameSingular="person"
|
||||||
|
>
|
||||||
|
<Story />
|
||||||
|
</RecordTableContextProvider>
|
||||||
|
),
|
||||||
ComponentDecorator,
|
ComponentDecorator,
|
||||||
MemoryRouterDecorator,
|
MemoryRouterDecorator,
|
||||||
ObjectMetadataItemsDecorator,
|
|
||||||
RecordTableDecorator,
|
RecordTableDecorator,
|
||||||
(Story) => (
|
ContextStoreDecorator,
|
||||||
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
|
SnackBarDecorator,
|
||||||
<RecordFiltersComponentInstanceContext.Provider
|
ObjectMetadataItemsDecorator,
|
||||||
value={{ instanceId: 'record-filters-component-instance' }}
|
I18nFrontDecorator,
|
||||||
>
|
|
||||||
<RecordTableComponentInstance
|
|
||||||
recordTableId="persons"
|
|
||||||
onColumnsChange={() => {}}
|
|
||||||
>
|
|
||||||
<Story />
|
|
||||||
</RecordTableComponentInstance>
|
|
||||||
</RecordFiltersComponentInstanceContext.Provider>
|
|
||||||
</SnackBarProviderScope>
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
parameters: {
|
parameters: {
|
||||||
|
recordTableObjectNameSingular: 'person',
|
||||||
msw: graphqlMocks,
|
msw: graphqlMocks,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,20 +5,20 @@ const StyledTbody = styled.tbody`
|
|||||||
td:nth-of-type(1) {
|
td:nth-of-type(1) {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 5;
|
z-index: 6;
|
||||||
transition: 0.3s ease;
|
transition: 0.3s ease;
|
||||||
}
|
}
|
||||||
td:nth-of-type(2) {
|
td:nth-of-type(2) {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
left: 11px;
|
left: 11px;
|
||||||
z-index: 5;
|
z-index: 6;
|
||||||
transition: 0.3s ease;
|
transition: 0.3s ease;
|
||||||
}
|
}
|
||||||
tr:not(:last-child) td:nth-of-type(3) {
|
tr:not(:last-child) td:nth-of-type(3) {
|
||||||
// Last row is aggregate footer
|
// Last row is aggregate footer
|
||||||
position: sticky;
|
position: sticky;
|
||||||
left: 43px;
|
left: 43px;
|
||||||
z-index: 5;
|
z-index: 6;
|
||||||
transition: 0.3s ease;
|
transition: 0.3s ease;
|
||||||
|
|
||||||
&:not(.disable-shadow)::after {
|
&:not(.disable-shadow)::after {
|
||||||
|
|||||||
@ -26,21 +26,21 @@ const StyledTableHead = styled.thead`
|
|||||||
th:nth-of-type(1) {
|
th:nth-of-type(1) {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 5;
|
z-index: 6;
|
||||||
transition: 0.3s ease;
|
transition: 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
th:nth-of-type(2) {
|
th:nth-of-type(2) {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
left: 11px;
|
left: 11px;
|
||||||
z-index: 5;
|
z-index: 6;
|
||||||
transition: 0.3s ease;
|
transition: 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
th:nth-of-type(3) {
|
th:nth-of-type(3) {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
left: 43px;
|
left: 43px;
|
||||||
z-index: 5;
|
z-index: 6;
|
||||||
transition: 0.3s ease;
|
transition: 0.3s ease;
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
@ -65,7 +65,7 @@ const StyledTableHead = styled.thead`
|
|||||||
th {
|
th {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 5;
|
z-index: 6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,41 +1,81 @@
|
|||||||
import { Decorator } from '@storybook/react';
|
import { Decorator } from '@storybook/react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||||
|
import { getActionMenuIdFromRecordIndexId } from '@/action-menu/utils/getActionMenuIdFromRecordIndexId';
|
||||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { RecordFilterGroupsComponentInstanceContext } from '@/object-record/record-filter-group/states/context/RecordFilterGroupsComponentInstanceContext';
|
||||||
|
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
|
||||||
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
|
import { RecordIndexContextProvider } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||||
|
import { useLoadRecordIndexStates } from '@/object-record/record-index/hooks/useLoadRecordIndexStates';
|
||||||
|
import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext';
|
||||||
|
import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||||
import { RecordTableBodyContextProvider } from '@/object-record/record-table/contexts/RecordTableBodyContext';
|
import { RecordTableBodyContextProvider } from '@/object-record/record-table/contexts/RecordTableBodyContext';
|
||||||
import { RecordTableContextProvider } from '@/object-record/record-table/contexts/RecordTableContext';
|
import { RecordTableContextProvider } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
|
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||||
|
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
||||||
|
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
import { ViewComponentInstanceContext } from '@/views/states/contexts/ViewComponentInstanceContext';
|
||||||
|
import { View } from '@/views/types/View';
|
||||||
|
import { useEffect, useMemo } from 'react';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { mockedViewFieldsData } from '~/testing/mock-data/view-fields';
|
||||||
|
import { mockedViewsData } from '~/testing/mock-data/views';
|
||||||
|
|
||||||
export const RecordTableDecorator: Decorator = (Story) => {
|
const InternalTableStateLoaderEffect = ({
|
||||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
objectMetadataItem,
|
||||||
|
}: {
|
||||||
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
|
}) => {
|
||||||
|
const { loadRecordIndexStates } = useLoadRecordIndexStates();
|
||||||
|
|
||||||
const personObjectMetadataItem = objectMetadataItems.find(
|
const view = useMemo(() => {
|
||||||
(objectMetadataItem) => objectMetadataItem.nameSingular === 'person',
|
return {
|
||||||
|
...mockedViewsData[0],
|
||||||
|
viewFields: mockedViewFieldsData.filter(
|
||||||
|
(viewField) => viewField.viewId === mockedViewsData[0].id,
|
||||||
|
),
|
||||||
|
} as unknown as View;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadRecordIndexStates(view, objectMetadataItem);
|
||||||
|
}, [loadRecordIndexStates, objectMetadataItem, view]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const InternalTableContextProviders = ({
|
||||||
|
children,
|
||||||
|
objectMetadataItem,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
|
}) => {
|
||||||
|
const visibleTableColumns = useRecoilComponentValueV2(
|
||||||
|
visibleTableColumnsComponentSelector,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isDefined(personObjectMetadataItem)) {
|
|
||||||
return <Story />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RecordIndexContextProvider
|
<RecordIndexContextProvider
|
||||||
value={{
|
value={{
|
||||||
indexIdentifierUrl: () => '',
|
indexIdentifierUrl: () => '',
|
||||||
onIndexRecordsLoaded: () => {},
|
onIndexRecordsLoaded: () => {},
|
||||||
objectNamePlural: personObjectMetadataItem.namePlural,
|
objectNamePlural: objectMetadataItem.namePlural,
|
||||||
objectNameSingular: personObjectMetadataItem.nameSingular,
|
objectNameSingular: objectMetadataItem.nameSingular,
|
||||||
objectMetadataItem: personObjectMetadataItem,
|
objectMetadataItem: objectMetadataItem,
|
||||||
recordIndexId: 'record-index',
|
recordIndexId: 'record-index',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RecordTableContextProvider
|
<RecordTableContextProvider
|
||||||
value={{
|
value={{
|
||||||
objectNameSingular: personObjectMetadataItem.nameSingular,
|
objectNameSingular: objectMetadataItem.nameSingular,
|
||||||
objectMetadataItem: personObjectMetadataItem,
|
objectMetadataItem: objectMetadataItem,
|
||||||
recordTableId: 'persons',
|
recordTableId: objectMetadataItem.namePlural,
|
||||||
viewBarId: 'view-bar',
|
viewBarId: 'view-bar',
|
||||||
visibleTableColumns: [],
|
visibleTableColumns: visibleTableColumns,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RecordTableBodyContextProvider
|
<RecordTableBodyContextProvider
|
||||||
@ -48,9 +88,71 @@ export const RecordTableDecorator: Decorator = (Story) => {
|
|||||||
onMoveSoftFocusToCurrentCell: () => {},
|
onMoveSoftFocusToCurrentCell: () => {},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Story />
|
{children}
|
||||||
</RecordTableBodyContextProvider>
|
</RecordTableBodyContextProvider>
|
||||||
</RecordTableContextProvider>
|
</RecordTableContextProvider>
|
||||||
</RecordIndexContextProvider>
|
</RecordIndexContextProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const RecordTableDecorator: Decorator = (Story, context) => {
|
||||||
|
const { recordTableObjectNameSingular: objectNameSingular } =
|
||||||
|
context.parameters;
|
||||||
|
|
||||||
|
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||||
|
|
||||||
|
const objectMetadataItem = objectMetadataItems.find(
|
||||||
|
(objectMetadataItem) =>
|
||||||
|
objectMetadataItem.nameSingular === objectNameSingular,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isDefined(objectMetadataItem)) {
|
||||||
|
throw new Error(
|
||||||
|
'Object metadata item not found while loading RecordTableDecorator',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const recordIndexId = getRecordIndexIdFromObjectNamePluralAndViewId(
|
||||||
|
objectMetadataItem.namePlural,
|
||||||
|
mockedViewsData[0].id,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RecordFieldValueSelectorContextProvider>
|
||||||
|
<RecordTableComponentInstanceContext.Provider
|
||||||
|
value={{ instanceId: recordIndexId, onColumnsChange: () => {} }}
|
||||||
|
>
|
||||||
|
<ViewComponentInstanceContext.Provider
|
||||||
|
value={{ instanceId: recordIndexId }}
|
||||||
|
>
|
||||||
|
<RecordFilterGroupsComponentInstanceContext.Provider
|
||||||
|
value={{ instanceId: recordIndexId }}
|
||||||
|
>
|
||||||
|
<RecordFiltersComponentInstanceContext.Provider
|
||||||
|
value={{ instanceId: recordIndexId }}
|
||||||
|
>
|
||||||
|
<RecordSortsComponentInstanceContext.Provider
|
||||||
|
value={{ instanceId: recordIndexId }}
|
||||||
|
>
|
||||||
|
<ActionMenuComponentInstanceContext.Provider
|
||||||
|
value={{
|
||||||
|
instanceId: getActionMenuIdFromRecordIndexId(recordIndexId),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<InternalTableStateLoaderEffect
|
||||||
|
objectMetadataItem={objectMetadataItem}
|
||||||
|
/>
|
||||||
|
<InternalTableContextProviders
|
||||||
|
objectMetadataItem={objectMetadataItem}
|
||||||
|
>
|
||||||
|
<Story />
|
||||||
|
</InternalTableContextProviders>
|
||||||
|
</ActionMenuComponentInstanceContext.Provider>
|
||||||
|
</RecordSortsComponentInstanceContext.Provider>
|
||||||
|
</RecordFiltersComponentInstanceContext.Provider>
|
||||||
|
</RecordFilterGroupsComponentInstanceContext.Provider>
|
||||||
|
</ViewComponentInstanceContext.Provider>
|
||||||
|
</RecordTableComponentInstanceContext.Provider>
|
||||||
|
</RecordFieldValueSelectorContextProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@ -12,6 +12,7 @@ const StyledContainer = styled.div`
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
min-height: 100px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface StyledImageProps {
|
interface StyledImageProps {
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
const StyledLayout = styled.div<{
|
const StyledLayout = styled.div<{
|
||||||
width?: number;
|
width?: number;
|
||||||
backgroundColor?: string | undefined;
|
backgroundColor?: string | undefined;
|
||||||
|
height: number | 'fit-content';
|
||||||
}>`
|
}>`
|
||||||
background: ${({ theme, backgroundColor }) =>
|
background: ${({ theme, backgroundColor }) =>
|
||||||
backgroundColor ?? theme.background.primary};
|
backgroundColor ?? theme.background.primary};
|
||||||
@ -12,7 +14,12 @@ const StyledLayout = styled.div<{
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
||||||
height: fit-content;
|
height: ${({ height }) =>
|
||||||
|
height === 'fit-content'
|
||||||
|
? 'fit-content'
|
||||||
|
: `
|
||||||
|
${height}px
|
||||||
|
`};
|
||||||
max-width: calc(100% - 40px);
|
max-width: calc(100% - 40px);
|
||||||
min-width: ${({ width }) => (width ? 'unset' : '300px')};
|
min-width: ${({ width }) => (width ? 'unset' : '300px')};
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
@ -22,15 +29,21 @@ const StyledLayout = styled.div<{
|
|||||||
type ComponentStorybookLayoutProps = {
|
type ComponentStorybookLayoutProps = {
|
||||||
width?: number;
|
width?: number;
|
||||||
backgroundColor?: string | undefined;
|
backgroundColor?: string | undefined;
|
||||||
|
height?: number;
|
||||||
children: JSX.Element;
|
children: JSX.Element;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ComponentStorybookLayout = ({
|
export const ComponentStorybookLayout = ({
|
||||||
width,
|
width,
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
|
height,
|
||||||
children,
|
children,
|
||||||
}: ComponentStorybookLayoutProps) => (
|
}: ComponentStorybookLayoutProps) => (
|
||||||
<StyledLayout width={width} backgroundColor={backgroundColor}>
|
<StyledLayout
|
||||||
|
width={width}
|
||||||
|
backgroundColor={backgroundColor}
|
||||||
|
height={isDefined(height) ? height : 'fit-content'}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</StyledLayout>
|
</StyledLayout>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -28,6 +28,7 @@ export const ComponentDecorator: Decorator = (Story, context) => {
|
|||||||
return (
|
return (
|
||||||
<ComponentStorybookLayout
|
<ComponentStorybookLayout
|
||||||
width={container?.width}
|
width={container?.width}
|
||||||
|
height={container?.height}
|
||||||
backgroundColor={backgroundColor}
|
backgroundColor={backgroundColor}
|
||||||
>
|
>
|
||||||
<Story />
|
<Story />
|
||||||
|
|||||||
Reference in New Issue
Block a user