feat: apply RecordDetailSection style on RecordDuplicatesSection and … (#4241)
feat: apply RecordDetailSection style on RecordDuplicatesSection and add stories Closes #3963, Closes #4240
This commit is contained in:
@ -1,5 +1,4 @@
|
|||||||
import { HelmetProvider } from 'react-helmet-async';
|
import { HelmetProvider } from 'react-helmet-async';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
|
||||||
import { Meta, StoryObj } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
import { ClientConfigProvider } from '@/client-config/components/ClientConfigProvider';
|
import { ClientConfigProvider } from '@/client-config/components/ClientConfigProvider';
|
||||||
@ -7,6 +6,7 @@ import { ObjectMetadataItemsProvider } from '@/object-metadata/components/Object
|
|||||||
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
|
import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/SnackBarProviderScope';
|
||||||
import { UserProvider } from '@/users/components/UserProvider';
|
import { UserProvider } from '@/users/components/UserProvider';
|
||||||
import { App } from '~/App';
|
import { App } from '~/App';
|
||||||
|
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||||
import { FullHeightStorybookLayout } from '~/testing/FullHeightStorybookLayout';
|
import { FullHeightStorybookLayout } from '~/testing/FullHeightStorybookLayout';
|
||||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||||
|
|
||||||
@ -14,20 +14,19 @@ const meta: Meta<typeof App> = {
|
|||||||
title: 'App/App',
|
title: 'App/App',
|
||||||
component: App,
|
component: App,
|
||||||
decorators: [
|
decorators: [
|
||||||
|
MemoryRouterDecorator,
|
||||||
(Story) => (
|
(Story) => (
|
||||||
<ClientConfigProvider>
|
<ClientConfigProvider>
|
||||||
<UserProvider>
|
<UserProvider>
|
||||||
<MemoryRouter>
|
<FullHeightStorybookLayout>
|
||||||
<FullHeightStorybookLayout>
|
<HelmetProvider>
|
||||||
<HelmetProvider>
|
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
|
||||||
<SnackBarProviderScope snackBarManagerScopeId="snack-bar-manager">
|
<ObjectMetadataItemsProvider>
|
||||||
<ObjectMetadataItemsProvider>
|
<Story />
|
||||||
<Story />
|
</ObjectMetadataItemsProvider>
|
||||||
</ObjectMetadataItemsProvider>
|
</SnackBarProviderScope>
|
||||||
</SnackBarProviderScope>
|
</HelmetProvider>
|
||||||
</HelmetProvider>
|
</FullHeightStorybookLayout>
|
||||||
</FullHeightStorybookLayout>
|
|
||||||
</MemoryRouter>
|
|
||||||
</UserProvider>
|
</UserProvider>
|
||||||
</ClientConfigProvider>
|
</ClientConfigProvider>
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { MemoryRouter } from 'react-router-dom';
|
|
||||||
import { Meta, StoryObj } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
import { TaskList } from '@/activities/tasks/components/TaskList';
|
import { TaskList } from '@/activities/tasks/components/TaskList';
|
||||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
|
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||||
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||||
import { mockedActivities } from '~/testing/mock-data/activities';
|
import { mockedActivities } from '~/testing/mock-data/activities';
|
||||||
@ -10,15 +10,7 @@ import { mockedActivities } from '~/testing/mock-data/activities';
|
|||||||
const meta: Meta<typeof TaskList> = {
|
const meta: Meta<typeof TaskList> = {
|
||||||
title: 'Modules/Activity/TaskList',
|
title: 'Modules/Activity/TaskList',
|
||||||
component: TaskList,
|
component: TaskList,
|
||||||
decorators: [
|
decorators: [MemoryRouterDecorator, ComponentDecorator, SnackBarDecorator],
|
||||||
(Story) => (
|
|
||||||
<MemoryRouter>
|
|
||||||
<Story />
|
|
||||||
</MemoryRouter>
|
|
||||||
),
|
|
||||||
ComponentDecorator,
|
|
||||||
SnackBarDecorator,
|
|
||||||
],
|
|
||||||
args: {
|
args: {
|
||||||
title: 'Tasks',
|
title: 'Tasks',
|
||||||
tasks: mockedActivities,
|
tasks: mockedActivities,
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { useQuery } from '@apollo/client';
|
|||||||
|
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||||
|
import { getFindDuplicateRecordsQueryResponseField } from '@/object-record/hooks/useGenerateFindDuplicateRecordsQuery';
|
||||||
import { useMapConnectionToRecords } from '@/object-record/hooks/useMapConnectionToRecords';
|
import { useMapConnectionToRecords } from '@/object-record/hooks/useMapConnectionToRecords';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
|
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
|
||||||
@ -29,6 +30,10 @@ export const useFindDuplicateRecords = <T extends ObjectRecord = ObjectRecord>({
|
|||||||
|
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSnackBar } = useSnackBar();
|
||||||
|
|
||||||
|
const queryResponseField = getFindDuplicateRecordsQueryResponseField(
|
||||||
|
objectMetadataItem.nameSingular,
|
||||||
|
);
|
||||||
|
|
||||||
const { data, loading, error } = useQuery<ObjectRecordQueryResult<T>>(
|
const { data, loading, error } = useQuery<ObjectRecordQueryResult<T>>(
|
||||||
findDuplicateRecordsQuery,
|
findDuplicateRecordsQuery,
|
||||||
{
|
{
|
||||||
@ -36,7 +41,7 @@ export const useFindDuplicateRecords = <T extends ObjectRecord = ObjectRecord>({
|
|||||||
id: objectRecordId,
|
id: objectRecordId,
|
||||||
},
|
},
|
||||||
onCompleted: (data) => {
|
onCompleted: (data) => {
|
||||||
onCompleted?.(data[objectMetadataItem.nameSingular]);
|
onCompleted?.(data[queryResponseField]);
|
||||||
},
|
},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
logError(
|
logError(
|
||||||
@ -53,8 +58,7 @@ export const useFindDuplicateRecords = <T extends ObjectRecord = ObjectRecord>({
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const objectRecordConnection =
|
const objectRecordConnection = data?.[queryResponseField];
|
||||||
data?.[`${objectMetadataItem.nameSingular}Duplicates`];
|
|
||||||
|
|
||||||
const mapConnectionToRecords = useMapConnectionToRecords();
|
const mapConnectionToRecords = useMapConnectionToRecords();
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,10 @@ import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMa
|
|||||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
import { capitalize } from '~/utils/string/capitalize';
|
import { capitalize } from '~/utils/string/capitalize';
|
||||||
|
|
||||||
|
export const getFindDuplicateRecordsQueryResponseField = (
|
||||||
|
objectNameSingular: string,
|
||||||
|
) => `${objectNameSingular}Duplicates`;
|
||||||
|
|
||||||
export const useGenerateFindDuplicateRecordsQuery = () => {
|
export const useGenerateFindDuplicateRecordsQuery = () => {
|
||||||
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
|
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
|
||||||
|
|
||||||
@ -15,7 +19,9 @@ export const useGenerateFindDuplicateRecordsQuery = () => {
|
|||||||
depth?: number;
|
depth?: number;
|
||||||
}) => gql`
|
}) => gql`
|
||||||
query FindDuplicate${capitalize(objectMetadataItem.nameSingular)}($id: ID) {
|
query FindDuplicate${capitalize(objectMetadataItem.nameSingular)}($id: ID) {
|
||||||
${objectMetadataItem.nameSingular}Duplicates(id: $id){
|
${getFindDuplicateRecordsQueryResponseField(
|
||||||
|
objectMetadataItem.nameSingular,
|
||||||
|
)}(id: $id) {
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
id
|
id
|
||||||
|
|||||||
@ -18,7 +18,7 @@ const mockedPersonObjectMetadataItem = {
|
|||||||
...mockedPeopleMetadata.node,
|
...mockedPeopleMetadata.node,
|
||||||
fields: mockedPeopleMetadata.node.fields.edges.map(({ node }) => node),
|
fields: mockedPeopleMetadata.node.fields.edges.map(({ node }) => node),
|
||||||
};
|
};
|
||||||
const mockedCompanyObjectMetadataItem = {
|
export const mockedCompanyObjectMetadataItem = {
|
||||||
...mockedCompaniesMetadata.node,
|
...mockedCompaniesMetadata.node,
|
||||||
fields: mockedCompaniesMetadata.node.fields.edges.map(({ node }) => node),
|
fields: mockedCompaniesMetadata.node.fields.edges.map(({ node }) => node),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
|
||||||
import { Meta, StoryObj } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
import { useSetRecoilState } from 'recoil';
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
import { ChipFieldDisplay } from '@/object-record/record-field/meta-types/display/components/ChipFieldDisplay';
|
import { ChipFieldDisplay } from '@/object-record/record-field/meta-types/display/components/ChipFieldDisplay';
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
|
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||||
import { FieldContext } from '../../../../contexts/FieldContext';
|
|
||||||
|
|
||||||
const ChipFieldValueSetterEffect = () => {
|
const ChipFieldValueSetterEffect = () => {
|
||||||
const setEntityFields = useSetRecoilState(recordStoreFamilyState('123'));
|
const setEntityFields = useSetRecoilState(recordStoreFamilyState('123'));
|
||||||
@ -28,30 +27,29 @@ const ChipFieldValueSetterEffect = () => {
|
|||||||
const meta: Meta = {
|
const meta: Meta = {
|
||||||
title: 'UI/Data/Field/Display/ChipFieldDisplay',
|
title: 'UI/Data/Field/Display/ChipFieldDisplay',
|
||||||
decorators: [
|
decorators: [
|
||||||
|
MemoryRouterDecorator,
|
||||||
(Story) => (
|
(Story) => (
|
||||||
<MemoryRouter>
|
<FieldContext.Provider
|
||||||
<FieldContext.Provider
|
value={{
|
||||||
value={{
|
entityId: '123',
|
||||||
entityId: '123',
|
basePathToShowPage: '/object-record/',
|
||||||
basePathToShowPage: '/object-record/',
|
isLabelIdentifier: false,
|
||||||
isLabelIdentifier: false,
|
fieldDefinition: {
|
||||||
fieldDefinition: {
|
fieldMetadataId: 'full name',
|
||||||
fieldMetadataId: 'full name',
|
label: 'Henry Cavill',
|
||||||
label: 'Henry Cavill',
|
type: 'FULL_NAME',
|
||||||
type: 'FULL_NAME',
|
iconName: 'IconCalendarEvent',
|
||||||
iconName: 'IconCalendarEvent',
|
metadata: {
|
||||||
metadata: {
|
fieldName: 'full name',
|
||||||
fieldName: 'full name',
|
objectMetadataNameSingular: 'person',
|
||||||
objectMetadataNameSingular: 'person',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
hotkeyScope: 'hotkey-scope',
|
},
|
||||||
}}
|
hotkeyScope: 'hotkey-scope',
|
||||||
>
|
}}
|
||||||
<ChipFieldValueSetterEffect />
|
>
|
||||||
<Story />
|
<ChipFieldValueSetterEffect />
|
||||||
</FieldContext.Provider>
|
<Story />
|
||||||
</MemoryRouter>
|
</FieldContext.Provider>
|
||||||
),
|
),
|
||||||
ComponentDecorator,
|
ComponentDecorator,
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
|
||||||
import { Meta, StoryObj } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
|
import { useEmailField } from '@/object-record/record-field/meta-types/hooks/useEmailField';
|
||||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
|
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||||
|
|
||||||
import { FieldContext } from '../../../../contexts/FieldContext';
|
|
||||||
import { useEmailField } from '../../../hooks/useEmailField';
|
|
||||||
import { EmailFieldDisplay } from '../EmailFieldDisplay';
|
import { EmailFieldDisplay } from '../EmailFieldDisplay';
|
||||||
|
|
||||||
const EmailFieldValueSetterEffect = ({ value }: { value: string }) => {
|
const EmailFieldValueSetterEffect = ({ value }: { value: string }) => {
|
||||||
@ -21,6 +21,7 @@ const EmailFieldValueSetterEffect = ({ value }: { value: string }) => {
|
|||||||
const meta: Meta = {
|
const meta: Meta = {
|
||||||
title: 'UI/Data/Field/Display/EmailFieldDisplay',
|
title: 'UI/Data/Field/Display/EmailFieldDisplay',
|
||||||
decorators: [
|
decorators: [
|
||||||
|
MemoryRouterDecorator,
|
||||||
(Story, { args }) => (
|
(Story, { args }) => (
|
||||||
<FieldContext.Provider
|
<FieldContext.Provider
|
||||||
value={{
|
value={{
|
||||||
@ -39,10 +40,8 @@ const meta: Meta = {
|
|||||||
hotkeyScope: 'hotkey-scope',
|
hotkeyScope: 'hotkey-scope',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MemoryRouter>
|
<EmailFieldValueSetterEffect value={args.value} />
|
||||||
<EmailFieldValueSetterEffect value={args.value} />
|
<Story />
|
||||||
<Story />
|
|
||||||
</MemoryRouter>
|
|
||||||
</FieldContext.Provider>
|
</FieldContext.Provider>
|
||||||
),
|
),
|
||||||
ComponentDecorator,
|
ComponentDecorator,
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
|
||||||
import { Meta, StoryObj } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
|
import { usePhoneField } from '@/object-record/record-field/meta-types/hooks/usePhoneField';
|
||||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
|
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||||
|
|
||||||
import { FieldContext } from '../../../../contexts/FieldContext';
|
|
||||||
import { usePhoneField } from '../../../hooks/usePhoneField';
|
|
||||||
import { PhoneFieldDisplay } from '../PhoneFieldDisplay';
|
import { PhoneFieldDisplay } from '../PhoneFieldDisplay';
|
||||||
|
|
||||||
const PhoneFieldValueSetterEffect = ({ value }: { value: string }) => {
|
const PhoneFieldValueSetterEffect = ({ value }: { value: string }) => {
|
||||||
@ -21,6 +21,7 @@ const PhoneFieldValueSetterEffect = ({ value }: { value: string }) => {
|
|||||||
const meta: Meta = {
|
const meta: Meta = {
|
||||||
title: 'UI/Data/Field/Display/PhoneFieldDisplay',
|
title: 'UI/Data/Field/Display/PhoneFieldDisplay',
|
||||||
decorators: [
|
decorators: [
|
||||||
|
MemoryRouterDecorator,
|
||||||
(Story, { args }) => (
|
(Story, { args }) => (
|
||||||
<FieldContext.Provider
|
<FieldContext.Provider
|
||||||
value={{
|
value={{
|
||||||
@ -41,10 +42,8 @@ const meta: Meta = {
|
|||||||
useUpdateRecord: () => [() => undefined, {}],
|
useUpdateRecord: () => [() => undefined, {}],
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MemoryRouter>
|
<PhoneFieldValueSetterEffect value={args.value} />
|
||||||
<PhoneFieldValueSetterEffect value={args.value} />
|
<Story />
|
||||||
<Story />
|
|
||||||
</MemoryRouter>
|
|
||||||
</FieldContext.Provider>
|
</FieldContext.Provider>
|
||||||
),
|
),
|
||||||
ComponentDecorator,
|
ComponentDecorator,
|
||||||
|
|||||||
@ -13,8 +13,8 @@ import {
|
|||||||
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
||||||
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
|
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
|
||||||
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
||||||
import { RecordDuplicatesFieldCardSection } from '@/object-record/record-show/record-detail-section/components/RecordDuplicatesFieldCardSection';
|
import { RecordDetailDuplicatesSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailDuplicatesSection';
|
||||||
import { RecordRelationFieldCardSection } from '@/object-record/record-show/record-detail-section/components/RecordRelationFieldCardSection';
|
import { RecordDetailRelationSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationSection';
|
||||||
import { recordLoadingFamilyState } from '@/object-record/record-store/states/recordLoadingFamilyState';
|
import { recordLoadingFamilyState } from '@/object-record/record-store/states/recordLoadingFamilyState';
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
import { recordStoreIdentifierFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreIdentifierSelector';
|
import { recordStoreIdentifierFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreIdentifierSelector';
|
||||||
@ -194,7 +194,7 @@ export const RecordShowContainer = ({
|
|||||||
</FieldContext.Provider>
|
</FieldContext.Provider>
|
||||||
))}
|
))}
|
||||||
</PropertyBox>
|
</PropertyBox>
|
||||||
<RecordDuplicatesFieldCardSection
|
<RecordDetailDuplicatesSection
|
||||||
objectRecordId={objectRecordId}
|
objectRecordId={objectRecordId}
|
||||||
objectNameSingular={objectNameSingular}
|
objectNameSingular={objectNameSingular}
|
||||||
/>
|
/>
|
||||||
@ -229,7 +229,7 @@ export const RecordShowContainer = ({
|
|||||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RecordRelationFieldCardSection />
|
<RecordDetailRelationSection />
|
||||||
</FieldContext.Provider>
|
</FieldContext.Provider>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -0,0 +1,37 @@
|
|||||||
|
import { RecordChip } from '@/object-record/components/RecordChip';
|
||||||
|
import { useFindDuplicateRecords } from '@/object-record/hooks/useFindDuplicateRecords';
|
||||||
|
import { RecordDetailRecordsList } from '@/object-record/record-show/record-detail-section/components/RecordDetailRecordsList';
|
||||||
|
import { RecordDetailRecordsListItem } from '@/object-record/record-show/record-detail-section/components/RecordDetailRecordsListItem';
|
||||||
|
import { RecordDetailSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailSection';
|
||||||
|
import { RecordDetailSectionHeader } from '@/object-record/record-show/record-detail-section/components/RecordDetailSectionHeader';
|
||||||
|
|
||||||
|
export const RecordDetailDuplicatesSection = ({
|
||||||
|
objectRecordId,
|
||||||
|
objectNameSingular,
|
||||||
|
}: {
|
||||||
|
objectRecordId: string;
|
||||||
|
objectNameSingular: string;
|
||||||
|
}) => {
|
||||||
|
const { records: duplicateRecords } = useFindDuplicateRecords({
|
||||||
|
objectRecordId,
|
||||||
|
objectNameSingular,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!duplicateRecords.length) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RecordDetailSection>
|
||||||
|
<RecordDetailSectionHeader title="Duplicates" />
|
||||||
|
<RecordDetailRecordsList>
|
||||||
|
{duplicateRecords.slice(0, 5).map((duplicateRecord) => (
|
||||||
|
<RecordDetailRecordsListItem key={duplicateRecord.id}>
|
||||||
|
<RecordChip
|
||||||
|
record={duplicateRecord}
|
||||||
|
objectNameSingular={objectNameSingular}
|
||||||
|
/>
|
||||||
|
</RecordDetailRecordsListItem>
|
||||||
|
))}
|
||||||
|
</RecordDetailRecordsList>
|
||||||
|
</RecordDetailSection>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
const StyledRecordsList = styled.div`
|
||||||
|
color: ${({ theme }) => theme.font.color.secondary};
|
||||||
|
overflow: hidden;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export { StyledRecordsList as RecordDetailRecordsList };
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
const StyledListItem = styled.div`
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
|
display: flex;
|
||||||
|
height: ${({ theme }) => theme.spacing(10)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export { StyledListItem as RecordDetailRecordsListItem };
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
import { RecordDetailRecordsList } from '@/object-record/record-show/record-detail-section/components/RecordDetailRecordsList';
|
||||||
|
import { RecordDetailRelationRecordsListItem } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListItem';
|
||||||
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
|
|
||||||
|
type RecordDetailRelationRecordsListProps = {
|
||||||
|
relationRecords: ObjectRecord[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RecordDetailRelationRecordsList = ({
|
||||||
|
relationRecords,
|
||||||
|
}: RecordDetailRelationRecordsListProps) => (
|
||||||
|
<RecordDetailRecordsList>
|
||||||
|
{relationRecords.slice(0, 5).map((relationRecord) => (
|
||||||
|
<RecordDetailRelationRecordsListItem
|
||||||
|
key={relationRecord.id}
|
||||||
|
relationRecord={relationRecord}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</RecordDetailRecordsList>
|
||||||
|
);
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
import { useTheme } from '@emotion/react';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { useIcons } from '@/ui/display/icon/hooks/useIcons';
|
||||||
|
|
||||||
|
type RecordDetailRelationRecordsListEmptyStateProps = {
|
||||||
|
relationObjectMetadataItem: ObjectMetadataItem;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledRelationRecordsListEmptyState = styled.div`
|
||||||
|
color: ${({ theme }) => theme.font.color.light};
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: ${({ theme }) => theme.spacing(2)};
|
||||||
|
display: flex;
|
||||||
|
height: ${({ theme }) => theme.spacing(10)};
|
||||||
|
text-transform: capitalize;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const RecordDetailRelationRecordsListEmptyState = ({
|
||||||
|
relationObjectMetadataItem,
|
||||||
|
}: RecordDetailRelationRecordsListEmptyStateProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const { getIcon } = useIcons();
|
||||||
|
const Icon = getIcon(relationObjectMetadataItem.icon);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledRelationRecordsListEmptyState>
|
||||||
|
<Icon size={theme.icon.size.sm} />
|
||||||
|
<div>No {relationObjectMetadataItem.labelSingular}</div>
|
||||||
|
</StyledRelationRecordsListEmptyState>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -11,35 +11,26 @@ import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
|||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
import { usePersistField } from '@/object-record/record-field/hooks/usePersistField';
|
import { usePersistField } from '@/object-record/record-field/hooks/usePersistField';
|
||||||
import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
|
import { RecordDetailRecordsListItem } from '@/object-record/record-show/record-detail-section/components/RecordDetailRecordsListItem';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { IconDotsVertical, IconTrash, IconUnlink } from '@/ui/display/icon';
|
import { IconDotsVertical, IconTrash, IconUnlink } from '@/ui/display/icon';
|
||||||
import { CardContent } from '@/ui/layout/card/components/CardContent';
|
|
||||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||||
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
||||||
|
|
||||||
const StyledCardContent = styled(CardContent)<{
|
const StyledListItem = styled(RecordDetailRecordsListItem)<{
|
||||||
isDropdownOpen?: boolean;
|
isDropdownOpen?: boolean;
|
||||||
}>`
|
}>`
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: ${({ theme }) => theme.spacing(1)};
|
|
||||||
display: flex;
|
|
||||||
height: ${({ theme }) => theme.spacing(10)};
|
|
||||||
padding: 0;
|
|
||||||
border: 0;
|
|
||||||
|
|
||||||
${({ isDropdownOpen, theme }) =>
|
${({ isDropdownOpen, theme }) =>
|
||||||
isDropdownOpen
|
!isDropdownOpen &&
|
||||||
? ''
|
css`
|
||||||
: css`
|
.displayOnHover {
|
||||||
.displayOnHover {
|
opacity: 0;
|
||||||
opacity: 0;
|
pointer-events: none;
|
||||||
pointer-events: none;
|
transition: opacity ${theme.animation.duration.instant}s ease;
|
||||||
transition: opacity ${theme.animation.duration.instant}s ease;
|
}
|
||||||
}
|
`}
|
||||||
`}
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
.displayOnHover {
|
.displayOnHover {
|
||||||
@ -49,15 +40,13 @@ const StyledCardContent = styled(CardContent)<{
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type RecordRelationFieldCardContentProps = {
|
type RecordDetailRelationRecordsListItemProps = {
|
||||||
divider?: boolean;
|
|
||||||
relationRecord: ObjectRecord;
|
relationRecord: ObjectRecord;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RecordRelationFieldCardContent = ({
|
export const RecordDetailRelationRecordsListItem = ({
|
||||||
divider,
|
|
||||||
relationRecord,
|
relationRecord,
|
||||||
}: RecordRelationFieldCardContentProps) => {
|
}: RecordDetailRelationRecordsListItemProps) => {
|
||||||
const { fieldDefinition } = useContext(FieldContext);
|
const { fieldDefinition } = useContext(FieldContext);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -124,7 +113,7 @@ export const RecordRelationFieldCardContent = ({
|
|||||||
CoreObjectNameSingular.WorkspaceMember;
|
CoreObjectNameSingular.WorkspaceMember;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledCardContent isDropdownOpen={isDropdownOpen} divider={divider}>
|
<StyledListItem isDropdownOpen={isDropdownOpen}>
|
||||||
<RecordChip
|
<RecordChip
|
||||||
record={relationRecord}
|
record={relationRecord}
|
||||||
objectNameSingular={relationObjectMetadataItem.nameSingular}
|
objectNameSingular={relationObjectMetadataItem.nameSingular}
|
||||||
@ -165,6 +154,6 @@ export const RecordRelationFieldCardContent = ({
|
|||||||
/>
|
/>
|
||||||
</DropdownScope>
|
</DropdownScope>
|
||||||
)}
|
)}
|
||||||
</StyledCardContent>
|
</StyledListItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -1,5 +1,4 @@
|
|||||||
import { useCallback, useContext } from 'react';
|
import { useCallback, useContext } from 'react';
|
||||||
import { useTheme } from '@emotion/react';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import qs from 'qs';
|
import qs from 'qs';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
@ -9,8 +8,10 @@ import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
|||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
import { usePersistField } from '@/object-record/record-field/hooks/usePersistField';
|
import { usePersistField } from '@/object-record/record-field/hooks/usePersistField';
|
||||||
import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
|
import { RecordDetailRelationRecordsList } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsList';
|
||||||
|
import { RecordDetailRelationRecordsListEmptyState } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListEmptyState';
|
||||||
|
import { RecordDetailSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailSection';
|
||||||
import { RecordDetailSectionHeader } from '@/object-record/record-show/record-detail-section/components/RecordDetailSectionHeader';
|
import { RecordDetailSectionHeader } from '@/object-record/record-show/record-detail-section/components/RecordDetailSectionHeader';
|
||||||
import { RecordRelationFieldCardContent } from '@/object-record/record-show/record-detail-section/components/RecordRelationFieldCardContent';
|
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
||||||
import { SingleEntitySelectMenuItemsWithSearch } from '@/object-record/relation-picker/components/SingleEntitySelectMenuItemsWithSearch';
|
import { SingleEntitySelectMenuItemsWithSearch } from '@/object-record/relation-picker/components/SingleEntitySelectMenuItemsWithSearch';
|
||||||
@ -19,12 +20,10 @@ import { RelationPickerScope } from '@/object-record/relation-picker/scopes/Rela
|
|||||||
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
|
import { EntityForSelect } from '@/object-record/relation-picker/types/EntityForSelect';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { IconForbid, IconPencil, IconPlus } from '@/ui/display/icon';
|
import { IconForbid, IconPencil, IconPlus } from '@/ui/display/icon';
|
||||||
import { useIcons } from '@/ui/display/icon/hooks/useIcons';
|
|
||||||
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
import { LightIconButton } from '@/ui/input/button/components/LightIconButton';
|
||||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||||
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
import { DropdownScope } from '@/ui/layout/dropdown/scopes/DropdownScope';
|
||||||
import { Section } from '@/ui/layout/section/components/Section';
|
|
||||||
import { FilterQueryParams } from '@/views/hooks/internal/useFiltersFromQueryParams';
|
import { FilterQueryParams } from '@/views/hooks/internal/useFiltersFromQueryParams';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||||
|
|
||||||
@ -32,30 +31,7 @@ const StyledAddDropdown = styled(Dropdown)`
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledCardNoContent = styled.div`
|
export const RecordDetailRelationSection = () => {
|
||||||
color: ${({ theme }) => theme.font.color.light};
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: ${({ theme }) => theme.spacing(2)};
|
|
||||||
display: flex;
|
|
||||||
height: ${({ theme }) => theme.spacing(10)};
|
|
||||||
text-transform: capitalize;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledCard = styled.div`
|
|
||||||
color: ${({ theme }) => theme.font.color.secondary};
|
|
||||||
overflow: hidden;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledSection = styled(Section)`
|
|
||||||
padding: ${({ theme }) => theme.spacing(3)};
|
|
||||||
border-top: 1px solid ${({ theme }) => theme.border.color.light};
|
|
||||||
width: unset;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const RecordRelationFieldCardSection = () => {
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const { entityId, fieldDefinition } = useContext(FieldContext);
|
const { entityId, fieldDefinition } = useContext(FieldContext);
|
||||||
const {
|
const {
|
||||||
fieldName,
|
fieldName,
|
||||||
@ -65,12 +41,10 @@ export const RecordRelationFieldCardSection = () => {
|
|||||||
} = fieldDefinition.metadata as FieldRelationMetadata;
|
} = fieldDefinition.metadata as FieldRelationMetadata;
|
||||||
const record = useRecoilValue(recordStoreFamilyState(entityId));
|
const record = useRecoilValue(recordStoreFamilyState(entityId));
|
||||||
|
|
||||||
const {
|
const { objectMetadataItem: relationObjectMetadataItem } =
|
||||||
labelIdentifierFieldMetadata: relationLabelIdentifierFieldMetadata,
|
useObjectMetadataItem({
|
||||||
objectMetadataItem: relationObjectMetadataItem,
|
objectNameSingular: relationObjectMetadataNameSingular,
|
||||||
} = useObjectMetadataItem({
|
});
|
||||||
objectNameSingular: relationObjectMetadataNameSingular,
|
|
||||||
});
|
|
||||||
|
|
||||||
const relationFieldMetadataItem = relationObjectMetadataItem.fields.find(
|
const relationFieldMetadataItem = relationObjectMetadataItem.fields.find(
|
||||||
({ id }) => id === relationFieldMetadataId,
|
({ id }) => id === relationFieldMetadataId,
|
||||||
@ -137,11 +111,8 @@ export const RecordRelationFieldCardSection = () => {
|
|||||||
relationObjectMetadataItem.namePlural
|
relationObjectMetadataItem.namePlural
|
||||||
}?${qs.stringify(filterQueryParams)}`;
|
}?${qs.stringify(filterQueryParams)}`;
|
||||||
|
|
||||||
const { getIcon } = useIcons();
|
|
||||||
const Icon = getIcon(relationObjectMetadataItem.icon);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledSection>
|
<RecordDetailSection>
|
||||||
<RecordDetailSectionHeader
|
<RecordDetailSectionHeader
|
||||||
title={fieldDefinition.label}
|
title={fieldDefinition.label}
|
||||||
link={
|
link={
|
||||||
@ -186,23 +157,13 @@ export const RecordRelationFieldCardSection = () => {
|
|||||||
</DropdownScope>
|
</DropdownScope>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{relationRecords.length === 0 && (
|
{relationRecords.length ? (
|
||||||
<StyledCardNoContent>
|
<RecordDetailRelationRecordsList relationRecords={relationRecords} />
|
||||||
<Icon size={theme.icon.size.sm} />
|
) : (
|
||||||
<div>No {relationObjectMetadataItem.labelSingular}</div>
|
<RecordDetailRelationRecordsListEmptyState
|
||||||
</StyledCardNoContent>
|
relationObjectMetadataItem={relationObjectMetadataItem}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{!!relationRecords.length && (
|
</RecordDetailSection>
|
||||||
<StyledCard>
|
|
||||||
{relationRecords.slice(0, 5).map((relationRecord, index) => (
|
|
||||||
<RecordRelationFieldCardContent
|
|
||||||
key={`${relationRecord.id}${relationLabelIdentifierFieldMetadata?.id}`}
|
|
||||||
divider={index < relationRecords.length - 1}
|
|
||||||
relationRecord={relationRecord}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</StyledCard>
|
|
||||||
)}
|
|
||||||
</StyledSection>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
import { Section } from '@/ui/layout/section/components/Section';
|
||||||
|
|
||||||
|
const StyledRecordDetailSection = styled(Section)`
|
||||||
|
border-top: 1px solid ${({ theme }) => theme.border.color.light};
|
||||||
|
padding: ${({ theme }) => theme.spacing(3)};
|
||||||
|
width: auto;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export { StyledRecordDetailSection as RecordDetailSection };
|
||||||
@ -1,51 +0,0 @@
|
|||||||
import styled from '@emotion/styled';
|
|
||||||
|
|
||||||
import { RecordChip } from '@/object-record/components/RecordChip';
|
|
||||||
import { useFindDuplicateRecords } from '@/object-record/hooks/useFindDuplicateRecords';
|
|
||||||
import { RecordDetailSectionHeader } from '@/object-record/record-show/record-detail-section/components/RecordDetailSectionHeader';
|
|
||||||
import { Card } from '@/ui/layout/card/components/Card';
|
|
||||||
import { CardContent } from '@/ui/layout/card/components/CardContent';
|
|
||||||
import { Section } from '@/ui/layout/section/components/Section';
|
|
||||||
|
|
||||||
const StyledCardContent = styled(CardContent)`
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
gap: ${({ theme }) => theme.spacing(4)};
|
|
||||||
padding: ${({ theme }) => theme.spacing(3)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const RecordDuplicatesFieldCardSection = ({
|
|
||||||
objectRecordId,
|
|
||||||
objectNameSingular,
|
|
||||||
}: {
|
|
||||||
objectRecordId: string;
|
|
||||||
objectNameSingular: string;
|
|
||||||
}) => {
|
|
||||||
const { records: duplicateRecords } = useFindDuplicateRecords({
|
|
||||||
objectRecordId,
|
|
||||||
objectNameSingular,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (duplicateRecords.length === 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Section>
|
|
||||||
<RecordDetailSectionHeader title="Duplicates" />
|
|
||||||
<Card>
|
|
||||||
{duplicateRecords.slice(0, 5).map((duplicateRecord, index) => (
|
|
||||||
<StyledCardContent
|
|
||||||
key={`${objectNameSingular}${duplicateRecord.id}`}
|
|
||||||
divider={index < duplicateRecords.length - 1}
|
|
||||||
>
|
|
||||||
<RecordChip
|
|
||||||
record={duplicateRecord}
|
|
||||||
objectNameSingular={objectNameSingular}
|
|
||||||
/>
|
|
||||||
</StyledCardContent>
|
|
||||||
))}
|
|
||||||
</Card>
|
|
||||||
</Section>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
|
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||||
|
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
||||||
|
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||||
|
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||||
|
import { mockedCompaniesData } from '~/testing/mock-data/companies';
|
||||||
|
|
||||||
|
import { RecordDetailDuplicatesSection } from '../RecordDetailDuplicatesSection';
|
||||||
|
|
||||||
|
const meta: Meta<typeof RecordDetailDuplicatesSection> = {
|
||||||
|
title:
|
||||||
|
'Modules/ObjectRecord/RecordShow/RecordDetailSection/RecordDetailDuplicatesSection',
|
||||||
|
component: RecordDetailDuplicatesSection,
|
||||||
|
decorators: [
|
||||||
|
ComponentDecorator,
|
||||||
|
ObjectMetadataItemsDecorator,
|
||||||
|
SnackBarDecorator,
|
||||||
|
MemoryRouterDecorator,
|
||||||
|
],
|
||||||
|
args: {
|
||||||
|
objectRecordId: mockedCompaniesData[0].id,
|
||||||
|
objectNameSingular: CoreObjectNameSingular.Company,
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
msw: graphqlMocks,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof RecordDetailDuplicatesSection>;
|
||||||
|
|
||||||
|
export const Default: Story = {};
|
||||||
@ -0,0 +1,67 @@
|
|||||||
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
|
import { formatFieldMetadataItemAsFieldDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition';
|
||||||
|
import { mockedCompanyObjectMetadataItem } from '@/object-record/record-field/__mocks__/fieldDefinitions';
|
||||||
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
|
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||||
|
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
||||||
|
import { RecordStoreDecorator } from '~/testing/decorators/RecordStoreDecorator';
|
||||||
|
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||||
|
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||||
|
import { mockedCompaniesData } from '~/testing/mock-data/companies';
|
||||||
|
import { mockedPeopleData } from '~/testing/mock-data/people';
|
||||||
|
|
||||||
|
import { RecordDetailRelationSection } from '../RecordDetailRelationSection';
|
||||||
|
|
||||||
|
const meta: Meta<typeof RecordDetailRelationSection> = {
|
||||||
|
title:
|
||||||
|
'Modules/ObjectRecord/RecordShow/RecordDetailSection/RecordDetailRelationSection',
|
||||||
|
component: RecordDetailRelationSection,
|
||||||
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<FieldContext.Provider
|
||||||
|
value={{
|
||||||
|
entityId: mockedCompaniesData[0].id,
|
||||||
|
basePathToShowPage: '/object-record/',
|
||||||
|
isLabelIdentifier: false,
|
||||||
|
fieldDefinition: formatFieldMetadataItemAsFieldDefinition({
|
||||||
|
field: mockedCompanyObjectMetadataItem.fields.find(
|
||||||
|
({ name }) => name === 'people',
|
||||||
|
)!,
|
||||||
|
objectMetadataItem: mockedCompanyObjectMetadataItem,
|
||||||
|
}),
|
||||||
|
hotkeyScope: 'hotkey-scope',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Story />
|
||||||
|
</FieldContext.Provider>
|
||||||
|
),
|
||||||
|
ComponentDecorator,
|
||||||
|
ObjectMetadataItemsDecorator,
|
||||||
|
SnackBarDecorator,
|
||||||
|
MemoryRouterDecorator,
|
||||||
|
],
|
||||||
|
parameters: {
|
||||||
|
msw: graphqlMocks,
|
||||||
|
records: mockedCompaniesData,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof RecordDetailRelationSection>;
|
||||||
|
|
||||||
|
export const EmptyState: Story = {};
|
||||||
|
|
||||||
|
export const WithRecords: Story = {
|
||||||
|
decorators: [RecordStoreDecorator],
|
||||||
|
parameters: {
|
||||||
|
records: [
|
||||||
|
{
|
||||||
|
...mockedCompaniesData[0],
|
||||||
|
people: { edges: mockedPeopleData.map((person) => ({ node: person })) },
|
||||||
|
},
|
||||||
|
...mockedPeopleData,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import { MemoryRouter } from 'react-router-dom';
|
|
||||||
import { Meta, StoryObj } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
import { Field, FieldMetadataType } from '~/generated-metadata/graphql';
|
import { Field, FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
|
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||||
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
||||||
import { RecordStoreDecorator } from '~/testing/decorators/RecordStoreDecorator';
|
import { RecordStoreDecorator } from '~/testing/decorators/RecordStoreDecorator';
|
||||||
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||||
@ -81,13 +81,7 @@ export const Date: Story = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Link: Story = {
|
export const Link: Story = {
|
||||||
decorators: [
|
decorators: [MemoryRouterDecorator],
|
||||||
(Story) => (
|
|
||||||
<MemoryRouter>
|
|
||||||
<Story />
|
|
||||||
</MemoryRouter>
|
|
||||||
),
|
|
||||||
],
|
|
||||||
args: {
|
args: {
|
||||||
fieldMetadata: mockedCompaniesMetadata.node.fields.edges.find(
|
fieldMetadata: mockedCompaniesMetadata.node.fields.edges.find(
|
||||||
({ node }) => node.type === FieldMetadataType.Link,
|
({ node }) => node.type === FieldMetadataType.Link,
|
||||||
@ -114,13 +108,7 @@ export const Rating: Story = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Relation: Story = {
|
export const Relation: Story = {
|
||||||
decorators: [
|
decorators: [MemoryRouterDecorator],
|
||||||
(Story) => (
|
|
||||||
<MemoryRouter>
|
|
||||||
<Story />
|
|
||||||
</MemoryRouter>
|
|
||||||
),
|
|
||||||
],
|
|
||||||
args: {
|
args: {
|
||||||
fieldMetadata: mockedPeopleMetadata.node.fields.edges.find(
|
fieldMetadata: mockedPeopleMetadata.node.fields.edges.find(
|
||||||
({ node }) => node.type === FieldMetadataType.Relation,
|
({ node }) => node.type === FieldMetadataType.Relation,
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { MemoryRouter } from 'react-router-dom';
|
|
||||||
import { Meta, StoryObj } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
import { userEvent, within } from '@storybook/test';
|
import { userEvent, within } from '@storybook/test';
|
||||||
import { fn } from '@storybook/test';
|
import { fn } from '@storybook/test';
|
||||||
@ -8,6 +7,7 @@ import {
|
|||||||
RelationMetadataType,
|
RelationMetadataType,
|
||||||
} from '~/generated-metadata/graphql';
|
} from '~/generated-metadata/graphql';
|
||||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
|
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||||
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
||||||
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||||
@ -78,13 +78,7 @@ const relationFieldMetadata = mockedPeopleMetadata.node.fields.edges.find(
|
|||||||
)!.node;
|
)!.node;
|
||||||
|
|
||||||
export const WithRelationForm: Story = {
|
export const WithRelationForm: Story = {
|
||||||
decorators: [
|
decorators: [MemoryRouterDecorator],
|
||||||
(Story) => (
|
|
||||||
<MemoryRouter>
|
|
||||||
<Story />
|
|
||||||
</MemoryRouter>
|
|
||||||
),
|
|
||||||
],
|
|
||||||
args: {
|
args: {
|
||||||
fieldMetadata: mockedCompaniesMetadata.node.fields.edges.find(
|
fieldMetadata: mockedCompaniesMetadata.node.fields.edges.find(
|
||||||
({ node }) => node.type === FieldMetadataType.Relation,
|
({ node }) => node.type === FieldMetadataType.Relation,
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { MemoryRouter } from 'react-router-dom';
|
|
||||||
import { Meta, StoryObj } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
import { useSetRecoilState } from 'recoil';
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { RecordTableScope } from '@/object-record/record-table/scopes/RecordTableScope';
|
import { RecordTableScope } from '@/object-record/record-table/scopes/RecordTableScope';
|
||||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
|
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||||
|
|
||||||
import { actionBarOpenState } from '../../states/actionBarIsOpenState';
|
import { actionBarOpenState } from '../../states/actionBarIsOpenState';
|
||||||
import { ActionBar } from '../ActionBar';
|
import { ActionBar } from '../ActionBar';
|
||||||
@ -18,14 +18,13 @@ const meta: Meta<typeof ActionBar> = {
|
|||||||
title: 'UI/Navigation/ActionBar/ActionBar',
|
title: 'UI/Navigation/ActionBar/ActionBar',
|
||||||
component: FilledActionBar,
|
component: FilledActionBar,
|
||||||
decorators: [
|
decorators: [
|
||||||
|
MemoryRouterDecorator,
|
||||||
(Story) => (
|
(Story) => (
|
||||||
<RecordTableScope
|
<RecordTableScope
|
||||||
recordTableScopeId="companies"
|
recordTableScopeId="companies"
|
||||||
onColumnsChange={() => {}}
|
onColumnsChange={() => {}}
|
||||||
>
|
>
|
||||||
<MemoryRouter>
|
<Story />
|
||||||
<Story />
|
|
||||||
</MemoryRouter>
|
|
||||||
</RecordTableScope>
|
</RecordTableScope>
|
||||||
),
|
),
|
||||||
ComponentDecorator,
|
ComponentDecorator,
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import { MemoryRouter } from 'react-router-dom';
|
|
||||||
import { Meta, StoryObj } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
import { useSetRecoilState } from 'recoil';
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { RecordTableScope } from '@/object-record/record-table/scopes/RecordTableScope';
|
import { RecordTableScope } from '@/object-record/record-table/scopes/RecordTableScope';
|
||||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
|
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||||
|
|
||||||
import { contextMenuIsOpenState } from '../../states/contextMenuIsOpenState';
|
import { contextMenuIsOpenState } from '../../states/contextMenuIsOpenState';
|
||||||
import { contextMenuPositionState } from '../../states/contextMenuPositionState';
|
import { contextMenuPositionState } from '../../states/contextMenuPositionState';
|
||||||
@ -24,14 +24,13 @@ const meta: Meta<typeof ContextMenu> = {
|
|||||||
title: 'UI/Navigation/ContextMenu/ContextMenu',
|
title: 'UI/Navigation/ContextMenu/ContextMenu',
|
||||||
component: FilledContextMenu,
|
component: FilledContextMenu,
|
||||||
decorators: [
|
decorators: [
|
||||||
|
MemoryRouterDecorator,
|
||||||
(Story) => (
|
(Story) => (
|
||||||
<RecordTableScope
|
<RecordTableScope
|
||||||
recordTableScopeId="companies"
|
recordTableScopeId="companies"
|
||||||
onColumnsChange={() => {}}
|
onColumnsChange={() => {}}
|
||||||
>
|
>
|
||||||
<MemoryRouter>
|
<Story />
|
||||||
<Story />
|
|
||||||
</MemoryRouter>
|
|
||||||
</RecordTableScope>
|
</RecordTableScope>
|
||||||
),
|
),
|
||||||
ComponentDecorator,
|
ComponentDecorator,
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import { MemoryRouter } from 'react-router-dom';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { Meta, StoryObj } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
import { IconSearch } from '@/ui/display/icon';
|
import { IconSearch } from '@/ui/display/icon';
|
||||||
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
||||||
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
|
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
|
||||||
|
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||||
import { CatalogStory } from '~/testing/types';
|
import { CatalogStory } from '~/testing/types';
|
||||||
|
|
||||||
import { NavigationDrawerItem } from '../NavigationDrawerItem';
|
import { NavigationDrawerItem } from '../NavigationDrawerItem';
|
||||||
@ -47,11 +47,7 @@ export const Catalog: CatalogStory<Story, typeof NavigationDrawerItem> = {
|
|||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
),
|
),
|
||||||
CatalogDecorator,
|
CatalogDecorator,
|
||||||
(Story) => (
|
MemoryRouterDecorator,
|
||||||
<MemoryRouter>
|
|
||||||
<Story />
|
|
||||||
</MemoryRouter>
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
parameters: {
|
parameters: {
|
||||||
pseudo: { hover: ['.hover'] },
|
pseudo: { hover: ['.hover'] },
|
||||||
|
|||||||
@ -0,0 +1,8 @@
|
|||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
import { Decorator } from '@storybook/react';
|
||||||
|
|
||||||
|
export const MemoryRouterDecorator: Decorator = (Story) => (
|
||||||
|
<MemoryRouter>
|
||||||
|
<Story />
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
@ -7,7 +7,10 @@ import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queri
|
|||||||
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
||||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||||
import { mockedActivities } from '~/testing/mock-data/activities';
|
import { mockedActivities } from '~/testing/mock-data/activities';
|
||||||
import { mockedCompaniesData } from '~/testing/mock-data/companies';
|
import {
|
||||||
|
mockedCompaniesData,
|
||||||
|
mockedDuplicateCompanyData,
|
||||||
|
} from '~/testing/mock-data/companies';
|
||||||
import { mockedClientConfig } from '~/testing/mock-data/config';
|
import { mockedClientConfig } from '~/testing/mock-data/config';
|
||||||
import { mockedPipelineSteps } from '~/testing/mock-data/pipeline-steps';
|
import { mockedPipelineSteps } from '~/testing/mock-data/pipeline-steps';
|
||||||
import { mockedUsersData } from '~/testing/mock-data/users';
|
import { mockedUsersData } from '~/testing/mock-data/users';
|
||||||
@ -140,6 +143,49 @@ export const graphqlMocks = {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
graphql.query('FindDuplicateCompany', () => {
|
||||||
|
return HttpResponse.json({
|
||||||
|
data: {
|
||||||
|
companyDuplicates: {
|
||||||
|
edges: [
|
||||||
|
{
|
||||||
|
node: {
|
||||||
|
...mockedDuplicateCompanyData,
|
||||||
|
favorites: {
|
||||||
|
edges: [],
|
||||||
|
__typename: 'FavoriteConnection',
|
||||||
|
},
|
||||||
|
attachments: {
|
||||||
|
edges: [],
|
||||||
|
__typename: 'AttachmentConnection',
|
||||||
|
},
|
||||||
|
people: {
|
||||||
|
edges: [],
|
||||||
|
__typename: 'PersonConnection',
|
||||||
|
},
|
||||||
|
opportunities: {
|
||||||
|
edges: [],
|
||||||
|
__typename: 'OpportunityConnection',
|
||||||
|
},
|
||||||
|
activityTargets: {
|
||||||
|
edges: [],
|
||||||
|
__typename: 'ActivityTargetConnection',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cursor: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pageInfo: {
|
||||||
|
hasNextPage: false,
|
||||||
|
hasPreviousPage: false,
|
||||||
|
startCursor: null,
|
||||||
|
endCursor: null,
|
||||||
|
},
|
||||||
|
totalCount: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
graphql.query('FindManyPeople', () => {
|
graphql.query('FindManyPeople', () => {
|
||||||
return HttpResponse.json({
|
return HttpResponse.json({
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@ -179,6 +179,11 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const mockedDuplicateCompanyData: MockedCompany = {
|
||||||
|
...mockedCompaniesData[0],
|
||||||
|
id: '8b40856a-2ec9-4c03-8bc0-c032c89e1824',
|
||||||
|
};
|
||||||
|
|
||||||
export const mockedEmptyCompanyData = {
|
export const mockedEmptyCompanyData = {
|
||||||
id: '9231e6ee-4cc2-4c7b-8c55-dff16f4d968a',
|
id: '9231e6ee-4cc2-4c7b-8c55-dff16f4d968a',
|
||||||
name: '',
|
name: '',
|
||||||
|
|||||||
Reference in New Issue
Block a user