mirror of
https://github.com/twentyhq/twenty.git
synced 2024-08-18 02:10:25 +03:00
Refactored all FieldDisplay types for performance optimization (#5768)
This PR is the second part of https://github.com/twentyhq/twenty/pull/5693. It optimizes all remaining field types. The observed improvements are : - x2 loading time improvement on table rows - more consistent render time Here's a summary of measured improvements, what's given here is the average of hundreds of renders with a React Profiler component. (in our Storybook performance stories) | Component | Before (µs) | After (µs) | | ----- | ------------- | --- | | TextFieldDisplay | 127 | 83 | | EmailFieldDisplay | 117 | 83 | | NumberFieldDisplay | 97 | 56 | | DateFieldDisplay | 240 | 52 | | CurrencyFieldDisplay | 236 | 110 | | FullNameFieldDisplay | 131 | 85 | | AddressFieldDisplay | 118 | 81 | | BooleanFieldDisplay | 130 | 100 | | JSONFieldDisplay | 248 | 49 | | LinksFieldDisplay | 1180 | 140 | | LinkFieldDisplay | 140 | 78 | | MultiSelectFieldDisplay | 770 | 130 | | SelectFieldDisplay | 230 | 87 |
This commit is contained in:
parent
007e0e8b0e
commit
03b3c8a67a
@ -131,6 +131,7 @@
|
||||
"lodash.upperfirst": "^4.3.1",
|
||||
"luxon": "^3.3.0",
|
||||
"microdiff": "^1.3.2",
|
||||
"moize": "^6.1.6",
|
||||
"nest-commander": "^3.12.0",
|
||||
"next": "14.0.4",
|
||||
"next-mdx-remote": "^4.4.1",
|
||||
|
@ -3,7 +3,7 @@ import { ThemeProvider } from '@emotion/react';
|
||||
import { Preview } from '@storybook/react';
|
||||
import { initialize, mswDecorator } from 'msw-storybook-addon';
|
||||
import { useDarkMode } from 'storybook-dark-mode';
|
||||
import { THEME_DARK, THEME_LIGHT } from 'twenty-ui';
|
||||
import { THEME_DARK, THEME_LIGHT, ThemeContextProvider } from 'twenty-ui';
|
||||
|
||||
import { RootDecorator } from '../src/testing/decorators/RootDecorator';
|
||||
import { mockedUserJWT } from '../src/testing/mock-data/jwt';
|
||||
@ -39,7 +39,9 @@ const preview: Preview = {
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<Story />
|
||||
<ThemeContextProvider theme={theme}>
|
||||
<Story />
|
||||
</ThemeContextProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
},
|
||||
|
@ -9,7 +9,7 @@ import { emailThreadIdWhenEmailThreadWasClosedState } from '@/activities/emails/
|
||||
import { CardContent } from '@/ui/layout/card/components/CardContent';
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { MessageChannelVisibility, TimelineThread } from '~/generated/graphql';
|
||||
import { formatToHumanReadableDate } from '~/utils';
|
||||
import { formatToHumanReadableDate } from '~/utils/date-utils';
|
||||
import { getImageAbsoluteURIOrBase64 } from '~/utils/image/getImageAbsoluteURIOrBase64';
|
||||
|
||||
const StyledCardContent = styled(CardContent)<{
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
GenericFieldContextType,
|
||||
} from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||
import { formatToHumanReadableDate } from '~/utils';
|
||||
import { formatToHumanReadableDate } from '~/utils/date-utils';
|
||||
|
||||
const StyledRow = styled.div`
|
||||
align-items: center;
|
||||
|
@ -13,6 +13,8 @@ import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWith
|
||||
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
||||
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { getCompaniesMock } from '~/testing/mock-data/companies';
|
||||
import { getPeopleMock } from '~/testing/mock-data/people';
|
||||
import {
|
||||
mockDefaultWorkspace,
|
||||
mockedWorkspaceMemberData,
|
||||
@ -21,6 +23,9 @@ import { sleep } from '~/testing/sleep';
|
||||
|
||||
import { CommandMenu } from '../CommandMenu';
|
||||
|
||||
const peopleMock = getPeopleMock();
|
||||
const companiesMock = getCompaniesMock();
|
||||
|
||||
const openTimeout = 50;
|
||||
|
||||
const meta: Meta<typeof CommandMenu> = {
|
||||
@ -94,8 +99,12 @@ export const MatchingPersonCompanyActivityCreateNavigate: Story = {
|
||||
const searchInput = await canvas.findByPlaceholderText('Search');
|
||||
await sleep(openTimeout);
|
||||
await userEvent.type(searchInput, 'n');
|
||||
expect(await canvas.findByText('Alexandre Prot')).toBeInTheDocument();
|
||||
expect(await canvas.findByText('Airbnb')).toBeInTheDocument();
|
||||
expect(
|
||||
await canvas.findByText(
|
||||
peopleMock[0].name.firstName + ' ' + peopleMock[0].name.lastName,
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(await canvas.findByText(companiesMock[0].name)).toBeInTheDocument();
|
||||
expect(await canvas.findByText('My very first note')).toBeInTheDocument();
|
||||
expect(await canvas.findByText('Create Note')).toBeInTheDocument();
|
||||
expect(await canvas.findByText('Go to Companies')).toBeInTheDocument();
|
||||
@ -119,7 +128,11 @@ export const AtleastMatchingOnePerson: Story = {
|
||||
const searchInput = await canvas.findByPlaceholderText('Search');
|
||||
await sleep(openTimeout);
|
||||
await userEvent.type(searchInput, 'alex');
|
||||
expect(await canvas.findByText('Alexandre Prot')).toBeInTheDocument();
|
||||
expect(
|
||||
await canvas.findByText(
|
||||
peopleMock[0].name.firstName + ' ' + peopleMock[0].name.lastName,
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -3,10 +3,12 @@ import {
|
||||
mockedObjectMetadataItems,
|
||||
mockedPersonObjectMetadataItem,
|
||||
} from '~/testing/mock-data/metadata';
|
||||
import { mockedPeopleData } from '~/testing/mock-data/people';
|
||||
import { getPeopleMock } from '~/testing/mock-data/people';
|
||||
|
||||
import { getRecordNodeFromRecord } from '../getRecordNodeFromRecord';
|
||||
|
||||
const peopleMock = getPeopleMock();
|
||||
|
||||
describe('getRecordNodeFromRecord', () => {
|
||||
it('computes relation records cache references by default', () => {
|
||||
// Given
|
||||
@ -19,7 +21,7 @@ describe('getRecordNodeFromRecord', () => {
|
||||
name: true,
|
||||
company: true,
|
||||
};
|
||||
const record = mockedPeopleData[0];
|
||||
const record = peopleMock[0];
|
||||
|
||||
// When
|
||||
const result = getRecordNodeFromRecord({
|
||||
@ -33,12 +35,12 @@ describe('getRecordNodeFromRecord', () => {
|
||||
expect(result).toEqual({
|
||||
__typename: 'Person',
|
||||
company: {
|
||||
__ref: 'Company:5c21e19e-e049-4393-8c09-3e3f8fb09ecb',
|
||||
__ref: `Company:${record.company.id}`,
|
||||
},
|
||||
name: {
|
||||
__typename: 'FullName',
|
||||
firstName: 'Alexandre',
|
||||
lastName: 'Prot',
|
||||
firstName: record.name.firstName,
|
||||
lastName: record.name.lastName,
|
||||
},
|
||||
});
|
||||
});
|
||||
@ -54,7 +56,7 @@ describe('getRecordNodeFromRecord', () => {
|
||||
name: true,
|
||||
company: true,
|
||||
};
|
||||
const record = mockedPeopleData[0];
|
||||
const record = peopleMock[0];
|
||||
const computeReferences = false;
|
||||
|
||||
// When
|
||||
@ -72,8 +74,8 @@ describe('getRecordNodeFromRecord', () => {
|
||||
company: record.company,
|
||||
name: {
|
||||
__typename: 'FullName',
|
||||
firstName: 'Alexandre',
|
||||
lastName: 'Prot',
|
||||
firstName: record.name.firstName,
|
||||
lastName: record.name.lastName,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { gql } from '@apollo/client';
|
||||
import { mockedPeopleData } from '~/testing/mock-data/people';
|
||||
import { getPeopleMock } from '~/testing/mock-data/people';
|
||||
|
||||
const peopleMock = getPeopleMock();
|
||||
|
||||
export const query = gql`
|
||||
query FindDuplicatePerson($id: ID!) {
|
||||
@ -49,11 +51,11 @@ export const responseData = {
|
||||
personDuplicates: {
|
||||
edges: [
|
||||
{
|
||||
node: { ...mockedPeopleData[0], updatedAt: '' },
|
||||
node: { ...peopleMock[0], updatedAt: '' },
|
||||
cursor: 'cursor1',
|
||||
},
|
||||
{
|
||||
node: { ...mockedPeopleData[1], updatedAt: '' },
|
||||
node: { ...peopleMock[1], updatedAt: '' },
|
||||
cursor: 'cursor2',
|
||||
},
|
||||
],
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { useAddressField } from '@/object-record/record-field/meta-types/hooks/useAddressField';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { useAddressFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useAddressFieldDisplay';
|
||||
import { TextDisplay } from '@/ui/field/display/components/TextDisplay';
|
||||
|
||||
export const AddressFieldDisplay = () => {
|
||||
const { fieldValue } = useAddressField();
|
||||
const { fieldValue } = useAddressFieldDisplay();
|
||||
|
||||
const content = [
|
||||
fieldValue?.addressStreet1,
|
||||
@ -10,7 +12,7 @@ export const AddressFieldDisplay = () => {
|
||||
fieldValue?.addressCity,
|
||||
fieldValue?.addressCountry,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.filter(isNonEmptyString)
|
||||
.join(', ');
|
||||
|
||||
return <TextDisplay text={content} />;
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { useBooleanField } from '@/object-record/record-field/meta-types/hooks/useBooleanField';
|
||||
import { useBooleanFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useBooleanFieldDisplay';
|
||||
import { BooleanDisplay } from '@/ui/field/display/components/BooleanDisplay';
|
||||
|
||||
export const BooleanFieldDisplay = () => {
|
||||
const { fieldValue } = useBooleanField();
|
||||
const { fieldValue } = useBooleanFieldDisplay();
|
||||
|
||||
return <BooleanDisplay value={fieldValue} />;
|
||||
};
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { useCurrencyFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useCurrencyFieldDisplay';
|
||||
import { CurrencyDisplay } from '@/ui/field/display/components/CurrencyDisplay';
|
||||
|
||||
import { useCurrencyField } from '../../hooks/useCurrencyField';
|
||||
|
||||
export const CurrencyFieldDisplay = () => {
|
||||
const { fieldValue } = useCurrencyField();
|
||||
const { fieldValue } = useCurrencyFieldDisplay();
|
||||
|
||||
return <CurrencyDisplay currencyValue={fieldValue} />;
|
||||
};
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { useDateFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useDateFieldDisplay';
|
||||
import { DateDisplay } from '@/ui/field/display/components/DateDisplay';
|
||||
|
||||
import { useDateField } from '../../hooks/useDateField';
|
||||
|
||||
export const DateFieldDisplay = () => {
|
||||
const { fieldValue } = useDateField();
|
||||
const { fieldValue } = useDateFieldDisplay();
|
||||
|
||||
return <DateDisplay value={fieldValue} />;
|
||||
};
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { useDateTimeFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useDateTimeFieldDisplay';
|
||||
import { DateTimeDisplay } from '@/ui/field/display/components/DateTimeDisplay';
|
||||
|
||||
import { useDateTimeField } from '../../hooks/useDateTimeField';
|
||||
|
||||
export const DateTimeFieldDisplay = () => {
|
||||
const { fieldValue } = useDateTimeField();
|
||||
const { fieldValue } = useDateTimeFieldDisplay();
|
||||
|
||||
return <DateTimeDisplay value={fieldValue} />;
|
||||
};
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { useEmailFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useEmailFieldDisplay';
|
||||
import { EmailDisplay } from '@/ui/field/display/components/EmailDisplay';
|
||||
|
||||
import { useEmailField } from '../../hooks/useEmailField';
|
||||
|
||||
export const EmailFieldDisplay = () => {
|
||||
const { fieldValue } = useEmailField();
|
||||
const { fieldValue } = useEmailFieldDisplay();
|
||||
|
||||
return <EmailDisplay value={fieldValue} />;
|
||||
};
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { useFullNameField } from '@/object-record/record-field/meta-types/hooks/useFullNameField';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { useFullNameFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useFullNameFieldDisplay';
|
||||
import { TextDisplay } from '@/ui/field/display/components/TextDisplay';
|
||||
|
||||
export const FullNameFieldDisplay = () => {
|
||||
const { fieldValue } = useFullNameField();
|
||||
const { fieldValue } = useFullNameFieldDisplay();
|
||||
|
||||
const content = [fieldValue.firstName, fieldValue.lastName]
|
||||
.filter(Boolean)
|
||||
const content = [fieldValue?.firstName, fieldValue?.lastName]
|
||||
.filter(isNonEmptyString)
|
||||
.join(' ');
|
||||
|
||||
return <TextDisplay text={content} />;
|
||||
|
@ -1,19 +1,15 @@
|
||||
import { useJsonField } from '@/object-record/record-field/meta-types/hooks/useJsonField';
|
||||
import { isFieldRawJsonValue } from '@/object-record/record-field/types/guards/isFieldRawJsonValue';
|
||||
import { useJsonFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useJsonFieldDisplay';
|
||||
import { JsonDisplay } from '@/ui/field/display/components/JsonDisplay';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const JsonFieldDisplay = () => {
|
||||
const { fieldValue, maxWidth } = useJsonField();
|
||||
const { fieldValue, maxWidth } = useJsonFieldDisplay();
|
||||
|
||||
return (
|
||||
<JsonDisplay
|
||||
text={
|
||||
isFieldRawJsonValue(fieldValue) && isDefined(fieldValue)
|
||||
? JSON.stringify(fieldValue)
|
||||
: ''
|
||||
}
|
||||
maxWidth={maxWidth}
|
||||
/>
|
||||
);
|
||||
if (!isDefined(fieldValue)) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const value = JSON.stringify(fieldValue);
|
||||
|
||||
return <JsonDisplay text={value} maxWidth={maxWidth} />;
|
||||
};
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { useLinkFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useLinkFieldDisplay';
|
||||
import { LinkDisplay } from '@/ui/field/display/components/LinkDisplay';
|
||||
|
||||
import { useLinkField } from '../../hooks/useLinkField';
|
||||
|
||||
export const LinkFieldDisplay = () => {
|
||||
const { fieldValue } = useLinkField();
|
||||
const { fieldValue } = useLinkFieldDisplay();
|
||||
|
||||
return <LinkDisplay value={fieldValue} />;
|
||||
};
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { useFieldFocus } from '@/object-record/record-field/hooks/useFieldFocus';
|
||||
import { useLinksField } from '@/object-record/record-field/meta-types/hooks/useLinksField';
|
||||
import { useLinksFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useLinksFieldDisplay';
|
||||
import { LinksDisplay } from '@/ui/field/display/components/LinksDisplay';
|
||||
|
||||
export const LinksFieldDisplay = () => {
|
||||
const { fieldValue } = useLinksField();
|
||||
const { fieldValue } = useLinksFieldDisplay();
|
||||
|
||||
const { isFocused } = useFieldFocus();
|
||||
|
||||
|
@ -1,23 +1,39 @@
|
||||
import { Tag } from 'twenty-ui';
|
||||
import { styled } from '@linaria/react';
|
||||
import { Tag, THEME_COMMON } from 'twenty-ui';
|
||||
|
||||
import { useFieldFocus } from '@/object-record/record-field/hooks/useFieldFocus';
|
||||
import { useMultiSelectField } from '@/object-record/record-field/meta-types/hooks/useMultiSelectField';
|
||||
import { useMultiSelectFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useMultiSelectFieldDisplay';
|
||||
import { ExpandableList } from '@/ui/layout/expandable-list/components/ExpandableList';
|
||||
|
||||
const spacing1 = THEME_COMMON.spacing(1);
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${spacing1};
|
||||
justify-content: flex-start;
|
||||
|
||||
max-width: 100%;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const MultiSelectFieldDisplay = () => {
|
||||
const { fieldValues, fieldDefinition } = useMultiSelectField();
|
||||
const { fieldValue, fieldDefinition } = useMultiSelectFieldDisplay();
|
||||
|
||||
const { isFocused } = useFieldFocus();
|
||||
|
||||
const selectedOptions = fieldValues
|
||||
const selectedOptions = fieldValue
|
||||
? fieldDefinition.metadata.options?.filter((option) =>
|
||||
fieldValues.includes(option.value),
|
||||
fieldValue.includes(option.value),
|
||||
)
|
||||
: [];
|
||||
|
||||
if (!selectedOptions) return null;
|
||||
|
||||
return (
|
||||
return isFocused ? (
|
||||
<ExpandableList isChipCountDisplayed={isFocused}>
|
||||
{selectedOptions.map((selectedOption, index) => (
|
||||
<Tag
|
||||
@ -27,5 +43,16 @@ export const MultiSelectFieldDisplay = () => {
|
||||
/>
|
||||
))}
|
||||
</ExpandableList>
|
||||
) : (
|
||||
<StyledContainer>
|
||||
{selectedOptions.map((selectedOption, index) => (
|
||||
<Tag
|
||||
preventShrink
|
||||
key={index}
|
||||
color={selectedOption.color}
|
||||
text={selectedOption.label}
|
||||
/>
|
||||
))}
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { useNumberFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useNumberFieldDisplay';
|
||||
import { NumberDisplay } from '@/ui/field/display/components/NumberDisplay';
|
||||
|
||||
import { useNumberField } from '../../hooks/useNumberField';
|
||||
|
||||
export const NumberFieldDisplay = () => {
|
||||
const { fieldValue } = useNumberField();
|
||||
const { fieldValue } = useNumberFieldDisplay();
|
||||
|
||||
return <NumberDisplay value={fieldValue} />;
|
||||
};
|
||||
|
@ -1,17 +1,24 @@
|
||||
import { Tag } from 'twenty-ui';
|
||||
|
||||
import { useSelectField } from '../../hooks/useSelectField';
|
||||
import { useSelectFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useSelectFieldDisplay';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const SelectFieldDisplay = () => {
|
||||
const { fieldValue, fieldDefinition } = useSelectField();
|
||||
const { fieldValue, fieldDefinition } = useSelectFieldDisplay();
|
||||
|
||||
const selectedOption = fieldDefinition.metadata.options?.find(
|
||||
(option) => option.value === fieldValue,
|
||||
);
|
||||
|
||||
return selectedOption ? (
|
||||
<Tag color={selectedOption.color} text={selectedOption.label} />
|
||||
) : (
|
||||
<></>
|
||||
if (!isDefined(selectedOption)) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tag
|
||||
preventShrink
|
||||
color={selectedOption.color}
|
||||
text={selectedOption.label}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { useTextFieldDisplay } from '@/object-record/record-field/meta-types/hooks/useTextFieldDisplay';
|
||||
import { TextDisplay } from '@/ui/field/display/components/TextDisplay';
|
||||
|
||||
import { useTextField } from '../../hooks/useTextField';
|
||||
|
||||
export const TextFieldDisplay = () => {
|
||||
const { fieldValue, maxWidth } = useTextField();
|
||||
const { fieldValue, maxWidth } = useTextFieldDisplay();
|
||||
|
||||
return <TextDisplay text={fieldValue} maxWidth={maxWidth} />;
|
||||
};
|
||||
|
@ -1,67 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
|
||||
import { FieldContext } from '../../../../contexts/FieldContext';
|
||||
import { useDateTimeField } from '../../../hooks/useDateTimeField';
|
||||
import { DateTimeFieldDisplay } from '../DateTimeFieldDisplay';
|
||||
|
||||
const formattedDate = new Date('2023-04-01');
|
||||
|
||||
const DateFieldValueSetterEffect = ({ value }: { value: string }) => {
|
||||
const { setFieldValue } = useDateTimeField();
|
||||
|
||||
useEffect(() => {
|
||||
setFieldValue(value);
|
||||
}, [setFieldValue, value]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/DateFieldDisplay',
|
||||
decorators: [
|
||||
(Story, { args }) => (
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
entityId: '',
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: {
|
||||
fieldMetadataId: 'date',
|
||||
label: 'Date',
|
||||
type: FieldMetadataType.DateTime,
|
||||
iconName: 'IconCalendarEvent',
|
||||
metadata: {
|
||||
fieldName: 'Date',
|
||||
objectMetadataNameSingular: 'person',
|
||||
},
|
||||
},
|
||||
hotkeyScope: 'hotkey-scope',
|
||||
}}
|
||||
>
|
||||
<DateFieldValueSetterEffect value={args.value} />
|
||||
<Story />
|
||||
</FieldContext.Provider>
|
||||
),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: DateTimeFieldDisplay,
|
||||
argTypes: { value: { control: 'date' } },
|
||||
args: {
|
||||
value: formattedDate,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof DateTimeFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Elipsis: Story = {
|
||||
parameters: {
|
||||
container: { width: 50 },
|
||||
},
|
||||
};
|
@ -1,67 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { useEmailField } from '@/object-record/record-field/meta-types/hooks/useEmailField';
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
|
||||
import { EmailFieldDisplay } from '../EmailFieldDisplay';
|
||||
|
||||
const EmailFieldValueSetterEffect = ({ value }: { value: string }) => {
|
||||
const { setFieldValue } = useEmailField();
|
||||
|
||||
useEffect(() => {
|
||||
setFieldValue(value);
|
||||
}, [setFieldValue, value]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/EmailFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
(Story, { args }) => (
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
entityId: '',
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: {
|
||||
fieldMetadataId: 'email',
|
||||
label: 'Email',
|
||||
type: FieldMetadataType.Email,
|
||||
iconName: 'IconLink',
|
||||
metadata: {
|
||||
fieldName: 'Email',
|
||||
placeHolder: 'Email',
|
||||
objectMetadataNameSingular: 'person',
|
||||
},
|
||||
},
|
||||
hotkeyScope: 'hotkey-scope',
|
||||
}}
|
||||
>
|
||||
<EmailFieldValueSetterEffect value={args.value} />
|
||||
<Story />
|
||||
</FieldContext.Provider>
|
||||
),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: EmailFieldDisplay,
|
||||
args: {
|
||||
value: 'Test@Test.test',
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof EmailFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Elipsis: Story = {
|
||||
parameters: {
|
||||
container: { width: 50 },
|
||||
},
|
||||
};
|
@ -1,83 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
|
||||
import { FieldContext } from '../../../../contexts/FieldContext';
|
||||
import { useNumberField } from '../../../hooks/useNumberField';
|
||||
import { NumberFieldDisplay } from '../NumberFieldDisplay';
|
||||
|
||||
const NumberFieldValueSetterEffect = ({ value }: { value: number }) => {
|
||||
const { setFieldValue } = useNumberField();
|
||||
|
||||
useEffect(() => {
|
||||
setFieldValue(value);
|
||||
}, [setFieldValue, value]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/NumberFieldDisplay',
|
||||
decorators: [
|
||||
(Story, { args }) => (
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
entityId: '',
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: {
|
||||
fieldMetadataId: 'number',
|
||||
label: 'Number',
|
||||
type: FieldMetadataType.Number,
|
||||
iconName: 'Icon123',
|
||||
metadata: {
|
||||
fieldName: 'Number',
|
||||
placeHolder: 'Number',
|
||||
isPositive: true,
|
||||
objectMetadataNameSingular: 'person',
|
||||
},
|
||||
},
|
||||
hotkeyScope: 'hotkey-scope',
|
||||
useUpdateRecord: () => [() => undefined, {}],
|
||||
}}
|
||||
>
|
||||
<NumberFieldValueSetterEffect value={args.value} />
|
||||
<Story />
|
||||
</FieldContext.Provider>
|
||||
),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: NumberFieldDisplay,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof NumberFieldDisplay>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
value: 100,
|
||||
},
|
||||
};
|
||||
|
||||
export const Elipsis: Story = {
|
||||
args: {
|
||||
value: 1e100,
|
||||
},
|
||||
parameters: {
|
||||
container: { width: 100 },
|
||||
},
|
||||
};
|
||||
|
||||
export const Negative: Story = {
|
||||
args: {
|
||||
value: -1000,
|
||||
},
|
||||
};
|
||||
|
||||
export const Float: Story = {
|
||||
args: {
|
||||
value: 1.357802,
|
||||
},
|
||||
};
|
@ -0,0 +1,54 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { AddressFieldDisplay } from '@/object-record/record-field/meta-types/display/components/AddressFieldDisplay';
|
||||
import { FieldAddressValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/AddressFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('person', 'testAddress'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: AddressFieldDisplay,
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof AddressFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Elipsis: Story = {
|
||||
parameters: {
|
||||
container: { width: 100 },
|
||||
},
|
||||
decorators: [
|
||||
getFieldDecorator('person', 'testAddress', {
|
||||
addressCity:
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam',
|
||||
addressCountry: 'United States',
|
||||
addressStreet1: '1234 Elm Street',
|
||||
addressStreet2: 'Apt 1234',
|
||||
addressLat: 0,
|
||||
addressLng: 0,
|
||||
addressPostcode: '12345',
|
||||
addressState: 'CA',
|
||||
} as FieldAddressValue),
|
||||
],
|
||||
};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'AddressFieldDisplay',
|
||||
averageThresholdInMs: 0.15,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
@ -0,0 +1,34 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { BooleanFieldDisplay } from '@/object-record/record-field/meta-types/display/components/BooleanFieldDisplay';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/BooleanFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('person', 'testBoolean'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: BooleanFieldDisplay,
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof BooleanFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'BooleanFieldDisplay',
|
||||
averageThresholdInMs: 0.15,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
@ -0,0 +1,64 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { CurrencyFieldDisplay } from '@/object-record/record-field/meta-types/display/components/CurrencyFieldDisplay';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/CurrencyFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('company', 'annualRecurringRevenue'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: CurrencyFieldDisplay,
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof CurrencyFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Millions: Story = {
|
||||
decorators: [
|
||||
getFieldDecorator('company', 'annualRecurringRevenue', {
|
||||
__typename: 'Currency',
|
||||
amountMicros: 18200000 * 1000000,
|
||||
currencyCode: 'EUR',
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export const Billions: Story = {
|
||||
decorators: [
|
||||
getFieldDecorator('company', 'annualRecurringRevenue', {
|
||||
__typename: 'Currency',
|
||||
amountMicros: 3230000000 * 1000000,
|
||||
currencyCode: 'USD',
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export const Bazillions: Story = {
|
||||
decorators: [
|
||||
getFieldDecorator('company', 'annualRecurringRevenue', {
|
||||
__typename: 'Currency',
|
||||
amountMicros: 1e100,
|
||||
currencyCode: 'USD',
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'CurrencyFieldDisplay',
|
||||
averageThresholdInMs: 0.2,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
@ -0,0 +1,40 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { DateFieldDisplay } from '@/object-record/record-field/meta-types/display/components/DateFieldDisplay';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/DateFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('person', 'createdAt'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: DateFieldDisplay,
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof DateFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Elipsis: Story = {
|
||||
parameters: {
|
||||
container: { width: 50 },
|
||||
},
|
||||
};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'DateFieldDisplay',
|
||||
averageThresholdInMs: 0.1,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
@ -0,0 +1,40 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { DateTimeFieldDisplay } from '@/object-record/record-field/meta-types/display/components/DateTimeFieldDisplay';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/DateTimeFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('person', 'createdAt'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: DateTimeFieldDisplay,
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof DateTimeFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Elipsis: Story = {
|
||||
parameters: {
|
||||
container: { width: 50 },
|
||||
},
|
||||
};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'DateTimeFieldDisplay',
|
||||
averageThresholdInMs: 0.1,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
@ -0,0 +1,47 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { EmailFieldDisplay } from '@/object-record/record-field/meta-types/display/components/EmailFieldDisplay';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/EmailFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('person', 'email'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: EmailFieldDisplay,
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof EmailFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Elipsis: Story = {
|
||||
parameters: {
|
||||
container: { width: 50 },
|
||||
},
|
||||
decorators: [
|
||||
getFieldDecorator(
|
||||
'person',
|
||||
'email',
|
||||
'asdasdasdaksjdhkajshdkajhasmdkamskdsd@asdkjhaksjdhaksjd.com',
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'EmailFieldDisplay',
|
||||
averageThresholdInMs: 0.5,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
@ -0,0 +1,40 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { FullNameFieldDisplay } from '@/object-record/record-field/meta-types/display/components/FullNameFieldDisplay';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/FullNameFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('person', 'name'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: FullNameFieldDisplay,
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof FullNameFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Elipsis: Story = {
|
||||
parameters: {
|
||||
container: { width: 50 },
|
||||
},
|
||||
};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'FullNameFieldDisplay',
|
||||
averageThresholdInMs: 0.5,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
@ -0,0 +1,40 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { JsonFieldDisplay } from '@/object-record/record-field/meta-types/display/components/JsonFieldDisplay';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/JsonFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('person', 'testJson'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: JsonFieldDisplay,
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof JsonFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Elipsis: Story = {
|
||||
parameters: {
|
||||
container: { width: 50 },
|
||||
},
|
||||
};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'JsonFieldDisplay',
|
||||
averageThresholdInMs: 0.1,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
@ -0,0 +1,34 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { LinkFieldDisplay } from '@/object-record/record-field/meta-types/display/components/LinkFieldDisplay';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/LinkFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('person', 'testLink'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: LinkFieldDisplay,
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof LinkFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'LinkFieldDisplay',
|
||||
averageThresholdInMs: 0.5,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
@ -0,0 +1,69 @@
|
||||
import { useContext, useEffect } from 'react';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { FieldFocusContext } from '@/object-record/record-field/contexts/FieldFocusContext';
|
||||
import { FieldFocusContextProvider } from '@/object-record/record-field/contexts/FieldFocusContextProvider';
|
||||
import { LinksFieldDisplay } from '@/object-record/record-field/meta-types/display/components/LinksFieldDisplay';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const FieldFocusEffect = () => {
|
||||
const { setIsFocused } = useContext(FieldFocusContext);
|
||||
|
||||
useEffect(() => {
|
||||
setIsFocused(true);
|
||||
}, [setIsFocused]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/LinksFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('person', 'testLinks'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: LinksFieldDisplay,
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof LinksFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const ExpandableList: Story = {
|
||||
decorators: [
|
||||
(Story) => {
|
||||
return (
|
||||
<FieldFocusContextProvider>
|
||||
<FieldFocusEffect />
|
||||
<Story />
|
||||
</FieldFocusContextProvider>
|
||||
);
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
container: { width: 100 },
|
||||
},
|
||||
};
|
||||
|
||||
export const Elipsis: Story = {
|
||||
parameters: {
|
||||
container: { width: 50 },
|
||||
},
|
||||
};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'LinksFieldDisplay',
|
||||
averageThresholdInMs: 0.5,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
@ -0,0 +1,69 @@
|
||||
import { useContext, useEffect } from 'react';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { FieldFocusContext } from '@/object-record/record-field/contexts/FieldFocusContext';
|
||||
import { FieldFocusContextProvider } from '@/object-record/record-field/contexts/FieldFocusContextProvider';
|
||||
import { MultiSelectFieldDisplay } from '@/object-record/record-field/meta-types/display/components/MultiSelectFieldDisplay';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const FieldFocusEffect = () => {
|
||||
const { setIsFocused } = useContext(FieldFocusContext);
|
||||
|
||||
useEffect(() => {
|
||||
setIsFocused(true);
|
||||
}, [setIsFocused]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/MultiSelectFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('person', 'testMultiSelect'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: MultiSelectFieldDisplay,
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof MultiSelectFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const ExpandableList: Story = {
|
||||
decorators: [
|
||||
(Story) => {
|
||||
return (
|
||||
<FieldFocusContextProvider>
|
||||
<FieldFocusEffect />
|
||||
<Story />
|
||||
</FieldFocusContextProvider>
|
||||
);
|
||||
},
|
||||
],
|
||||
parameters: {
|
||||
container: { width: 130 },
|
||||
},
|
||||
};
|
||||
|
||||
export const Elipsis: Story = {
|
||||
parameters: {
|
||||
container: { width: 50 },
|
||||
},
|
||||
};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'MultiSelectFieldDisplay',
|
||||
averageThresholdInMs: 0.2,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
@ -0,0 +1,53 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { NumberFieldDisplay } from '@/object-record/record-field/meta-types/display/components/NumberFieldDisplay';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/NumberFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('company', 'employees'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: NumberFieldDisplay,
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof NumberFieldDisplay>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
value: 100,
|
||||
},
|
||||
};
|
||||
|
||||
export const Elipsis: Story = {
|
||||
decorators: [getFieldDecorator('company', 'employees', 1e100)],
|
||||
parameters: {
|
||||
container: { width: 100 },
|
||||
},
|
||||
};
|
||||
|
||||
export const Negative: Story = {
|
||||
decorators: [getFieldDecorator('company', 'employees', -1000)],
|
||||
};
|
||||
|
||||
export const Float: Story = {
|
||||
decorators: [getFieldDecorator('company', 'employees', 3.14159)],
|
||||
};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'NumberFieldDisplay',
|
||||
averageThresholdInMs: 0.5,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
@ -33,9 +33,6 @@ export const Elipsis: Story = {
|
||||
};
|
||||
|
||||
export const WrongNumber: Story = {
|
||||
parameters: {
|
||||
container: { width: 50 },
|
||||
},
|
||||
decorators: [getFieldDecorator('person', 'phone', 'sdklaskdj')],
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,40 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { SelectFieldDisplay } from '@/object-record/record-field/meta-types/display/components/SelectFieldDisplay';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/SelectFieldDisplay',
|
||||
decorators: [
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('person', 'testSelect'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: SelectFieldDisplay,
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof SelectFieldDisplay>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Elipsis: Story = {
|
||||
parameters: {
|
||||
container: { width: 50 },
|
||||
},
|
||||
};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'SelectFieldDisplay',
|
||||
averageThresholdInMs: 0.2,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
@ -1,54 +1,22 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
|
||||
import { FieldContext } from '../../../../contexts/FieldContext';
|
||||
import { useTextField } from '../../../hooks/useTextField';
|
||||
import { TextFieldDisplay } from '../TextFieldDisplay';
|
||||
|
||||
const TextFieldValueSetterEffect = ({ value }: { value: string }) => {
|
||||
const { setFieldValue } = useTextField();
|
||||
|
||||
useEffect(() => {
|
||||
setFieldValue(value);
|
||||
}, [setFieldValue, value]);
|
||||
|
||||
return null;
|
||||
};
|
||||
import { TextFieldDisplay } from '@/object-record/record-field/meta-types/display/components/TextFieldDisplay';
|
||||
import { getFieldDecorator } from '~/testing/decorators/getFieldDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { getProfilingStory } from '~/testing/profiling/utils/getProfilingStory';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Display/TextFieldDisplay',
|
||||
decorators: [
|
||||
(Story, { args }) => (
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
entityId: '',
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: {
|
||||
fieldMetadataId: 'text',
|
||||
label: 'Text',
|
||||
type: FieldMetadataType.Text,
|
||||
iconName: 'IconLink',
|
||||
metadata: {
|
||||
fieldName: 'Text',
|
||||
placeHolder: 'Text',
|
||||
objectMetadataNameSingular: 'person',
|
||||
},
|
||||
},
|
||||
hotkeyScope: 'hotkey-scope',
|
||||
}}
|
||||
>
|
||||
<TextFieldValueSetterEffect value={args.value} />
|
||||
<Story />
|
||||
</FieldContext.Provider>
|
||||
),
|
||||
MemoryRouterDecorator,
|
||||
getFieldDecorator('person', 'city'),
|
||||
ComponentDecorator,
|
||||
],
|
||||
component: TextFieldDisplay,
|
||||
args: {
|
||||
value: 'Lorem ipsum',
|
||||
args: {},
|
||||
parameters: {
|
||||
chromatic: { disableSnapshot: true },
|
||||
},
|
||||
};
|
||||
|
||||
@ -59,11 +27,21 @@ type Story = StoryObj<typeof TextFieldDisplay>;
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Elipsis: Story = {
|
||||
args: {
|
||||
value:
|
||||
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Recusandae rerum fugiat veniam illum accusantium saepe, voluptate inventore libero doloribus doloremque distinctio blanditiis amet quis dolor a nulla? Placeat nam itaque rerum esse quidem animi, temporibus saepe debitis commodi quia eius eos minus inventore. Voluptates fugit optio sit ab consectetur ipsum, neque eius atque blanditiis. Ullam provident at porro minima, nobis vero dicta consequatur maxime laboriosam fugit repudiandae repellat tempore voluptas non voluptatibus neque aliquam ducimus doloribus ipsa? Sapiente suscipit unde modi commodi possimus doloribus eum voluptatibus, architecto laudantium, magnam, eos numquam exercitationem est maxime explicabo odio nemo qui distinctio temporibus.',
|
||||
},
|
||||
parameters: {
|
||||
container: { width: 100 },
|
||||
},
|
||||
decorators: [
|
||||
getFieldDecorator(
|
||||
'person',
|
||||
'city',
|
||||
'Lorem ipsum dolor sit amet consectetur adipisicing elit. Recusandae rerum fugiat veniam illum accusantium saepe, voluptate inventore libero doloribus doloremque distinctio blanditiis amet quis dolor a nulla? Placeat nam itaque rerum esse quidem animi, temporibus saepe debitis commodi quia eius eos minus inventore. Voluptates fugit optio sit ab consectetur ipsum, neque eius atque blanditiis. Ullam provident at porro minima, nobis vero dicta consequatur maxime laboriosam fugit repudiandae repellat tempore voluptas non voluptatibus neque aliquam ducimus doloribus ipsa? Sapiente suscipit unde modi commodi possimus doloribus eum voluptatibus, architecto laudantium, magnam, eos numquam exercitationem est maxime explicabo odio nemo qui distinctio temporibus.',
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
export const Performance = getProfilingStory({
|
||||
componentName: 'TextFieldDisplay',
|
||||
averageThresholdInMs: 0.5,
|
||||
numberOfRuns: 50,
|
||||
numberOfTestsPerRun: 100,
|
||||
});
|
@ -0,0 +1,22 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
import { FieldAddressValue } from '../../types/FieldMetadata';
|
||||
|
||||
export const useAddressFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
const fieldValue = useRecordFieldValue<FieldAddressValue | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldDefinition,
|
||||
fieldValue,
|
||||
};
|
||||
};
|
@ -0,0 +1,21 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
|
||||
export const useBooleanFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
const fieldValue = useRecordFieldValue<boolean | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldDefinition,
|
||||
fieldValue,
|
||||
};
|
||||
};
|
@ -0,0 +1,22 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
import { FieldCurrencyValue } from '../../types/FieldMetadata';
|
||||
|
||||
export const useCurrencyFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
const fieldValue = useRecordFieldValue<FieldCurrencyValue | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldDefinition,
|
||||
fieldValue,
|
||||
};
|
||||
};
|
@ -0,0 +1,24 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
|
||||
export const useDateFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition, hotkeyScope, clearable } =
|
||||
useContext(FieldContext);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
const fieldValue = useRecordFieldValue<string | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldDefinition,
|
||||
fieldValue,
|
||||
hotkeyScope,
|
||||
clearable,
|
||||
};
|
||||
};
|
@ -0,0 +1,24 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
|
||||
export const useDateTimeFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition, hotkeyScope, clearable } =
|
||||
useContext(FieldContext);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
const fieldValue = useRecordFieldValue<string | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldDefinition,
|
||||
fieldValue,
|
||||
hotkeyScope,
|
||||
clearable,
|
||||
};
|
||||
};
|
@ -0,0 +1,22 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
|
||||
export const useEmailFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
const fieldValue = useRecordFieldValue<string | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldDefinition,
|
||||
fieldValue,
|
||||
hotkeyScope,
|
||||
};
|
||||
};
|
@ -0,0 +1,22 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { FieldFullNameValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
|
||||
export const useFullNameFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
const fieldValue = useRecordFieldValue<FieldFullNameValue | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldDefinition,
|
||||
fieldValue,
|
||||
};
|
||||
};
|
@ -0,0 +1,23 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { FieldJsonValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
|
||||
export const useJsonFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition, maxWidth } = useContext(FieldContext);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
const fieldValue = useRecordFieldValue<FieldJsonValue | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
maxWidth,
|
||||
fieldDefinition,
|
||||
fieldValue,
|
||||
};
|
||||
};
|
@ -0,0 +1,21 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
import { FieldLinkValue } from '../../types/FieldMetadata';
|
||||
|
||||
export const useLinkFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
const fieldValue = useRecordFieldValue<FieldLinkValue | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldDefinition,
|
||||
fieldValue,
|
||||
};
|
||||
};
|
@ -0,0 +1,22 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { FieldLinksValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
|
||||
export const useLinksFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
const fieldValue = useRecordFieldValue<FieldLinksValue | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldDefinition,
|
||||
fieldValue,
|
||||
};
|
||||
};
|
@ -0,0 +1,26 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||
import {
|
||||
FieldMultiSelectMetadata,
|
||||
FieldMultiSelectValue,
|
||||
} from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
export const useMultiSelectFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
const { fieldName } = fieldDefinition.metadata;
|
||||
|
||||
const fieldValue = useRecordFieldValue<FieldMultiSelectValue | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldDefinition:
|
||||
fieldDefinition as FieldDefinition<FieldMultiSelectMetadata>,
|
||||
fieldValue,
|
||||
};
|
||||
};
|
@ -0,0 +1,26 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata';
|
||||
import { isFieldNumber } from '../../types/guards/isFieldNumber';
|
||||
|
||||
export const useNumberFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext);
|
||||
|
||||
assertFieldMetadata(FieldMetadataType.Number, isFieldNumber, fieldDefinition);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
const fieldValue = useRecordFieldValue<number | null | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldDefinition,
|
||||
fieldValue,
|
||||
hotkeyScope,
|
||||
};
|
||||
};
|
@ -9,7 +9,10 @@ export const usePhoneFieldDisplay = () => {
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
const fieldValue = useRecordFieldValue(entityId, fieldName);
|
||||
const fieldValue = useRecordFieldValue<string | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldDefinition,
|
||||
|
@ -3,6 +3,7 @@ import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { PreComputedChipGeneratorsContext } from '@/object-metadata/context/PreComputedChipGeneratorsContext';
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { FIELD_EDIT_BUTTON_WIDTH } from '@/ui/field/display/constants/FieldEditButtonWidth';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
@ -32,7 +33,10 @@ export const useRelationFieldDisplay = () => {
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
const fieldValue = useRecordFieldValue(entityId, fieldName);
|
||||
const fieldValue = useRecordFieldValue<ObjectRecord | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
const maxWidthForField =
|
||||
isDefined(button) && isDefined(maxWidth)
|
||||
|
@ -0,0 +1,26 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
import {
|
||||
FieldSelectMetadata,
|
||||
FieldSelectValue,
|
||||
} from '../../types/FieldMetadata';
|
||||
|
||||
export const useSelectFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
const { fieldName } = fieldDefinition.metadata;
|
||||
|
||||
const fieldValue = useRecordFieldValue<FieldSelectValue | undefined>(
|
||||
entityId,
|
||||
fieldName,
|
||||
);
|
||||
|
||||
return {
|
||||
fieldDefinition: fieldDefinition as FieldDefinition<FieldSelectMetadata>,
|
||||
fieldValue,
|
||||
};
|
||||
};
|
@ -0,0 +1,22 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { useRecordFieldValue } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
|
||||
export const useTextFieldDisplay = () => {
|
||||
const { entityId, fieldDefinition, hotkeyScope, maxWidth } =
|
||||
useContext(FieldContext);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
const fieldValue =
|
||||
useRecordFieldValue<string | undefined>(entityId, fieldName) ?? '';
|
||||
|
||||
return {
|
||||
maxWidth,
|
||||
fieldDefinition,
|
||||
fieldValue,
|
||||
hotkeyScope,
|
||||
};
|
||||
};
|
@ -1,20 +1,26 @@
|
||||
import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
||||
import { mockedCompaniesData } from '~/testing/mock-data/companies';
|
||||
import { mockObjectMetadataItem } from '~/testing/mock-data/objectMetadataItems';
|
||||
import { getCompaniesMock } from '~/testing/mock-data/companies';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems';
|
||||
|
||||
import { isRecordMatchingFilter } from './isRecordMatchingFilter';
|
||||
|
||||
const companiesMock = getCompaniesMock();
|
||||
|
||||
const companyMockObjectMetadataItem = generatedMockObjectMetadataItems.find(
|
||||
(item) => item.nameSingular === 'company',
|
||||
)!;
|
||||
|
||||
describe('isRecordMatchingFilter', () => {
|
||||
describe('Empty Filters', () => {
|
||||
it('matches any record when no filter is provided', () => {
|
||||
const emptyFilter = {};
|
||||
|
||||
mockedCompaniesData.forEach((company) => {
|
||||
companiesMock.forEach((company) => {
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: company,
|
||||
filter: emptyFilter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
@ -26,12 +32,12 @@ describe('isRecordMatchingFilter', () => {
|
||||
employees: {},
|
||||
};
|
||||
|
||||
mockedCompaniesData.forEach((company) => {
|
||||
companiesMock.forEach((company) => {
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: company,
|
||||
filter: filterWithEmptyFields,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
@ -40,12 +46,12 @@ describe('isRecordMatchingFilter', () => {
|
||||
it('matches any record with an empty and filter', () => {
|
||||
const filter = { and: [] };
|
||||
|
||||
mockedCompaniesData.forEach((company) => {
|
||||
companiesMock.forEach((company) => {
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: company,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
@ -54,12 +60,12 @@ describe('isRecordMatchingFilter', () => {
|
||||
it('matches any record with an empty or filter', () => {
|
||||
const filter = { or: [] };
|
||||
|
||||
mockedCompaniesData.forEach((company) => {
|
||||
companiesMock.forEach((company) => {
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: company,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
@ -68,12 +74,12 @@ describe('isRecordMatchingFilter', () => {
|
||||
it('matches any record with an empty not filter', () => {
|
||||
const filter = { not: {} };
|
||||
|
||||
mockedCompaniesData.forEach((company) => {
|
||||
companiesMock.forEach((company) => {
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: company,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
@ -82,92 +88,161 @@ describe('isRecordMatchingFilter', () => {
|
||||
|
||||
describe('Simple Filters', () => {
|
||||
it('matches a record with a simple equality filter on name', () => {
|
||||
const filter = { name: { eq: 'Airbnb' } };
|
||||
const companyMockInFilter = {
|
||||
...companiesMock[0],
|
||||
};
|
||||
|
||||
const companyMockNotInFilter = {
|
||||
...companiesMock[0],
|
||||
name: companyMockInFilter.name + 'Different',
|
||||
};
|
||||
|
||||
const filter = { name: { eq: companyMockInFilter.name } };
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[0],
|
||||
record: companyMockInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[1],
|
||||
record: companyMockNotInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('matches a record with a simple equality filter on domainName', () => {
|
||||
const filter = { domainName: { eq: 'airbnb.com' } };
|
||||
const companyMockInFilter = {
|
||||
...companiesMock[0],
|
||||
};
|
||||
|
||||
const companyMockNotInFilter = {
|
||||
...companiesMock[0],
|
||||
domainName: companyMockInFilter.domainName + 'Different',
|
||||
};
|
||||
|
||||
const filter = { domainName: { eq: companyMockInFilter.domainName } };
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[0],
|
||||
record: companyMockInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[1],
|
||||
record: companyMockNotInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('matches a record with a greater than filter on employees', () => {
|
||||
const filter = { employees: { gt: 10 } };
|
||||
const companyMockInFilter = {
|
||||
...companiesMock[0],
|
||||
employees: 100,
|
||||
};
|
||||
|
||||
const companyMockNotInFilter = {
|
||||
...companiesMock[0],
|
||||
employees: companyMockInFilter.employees - 50,
|
||||
};
|
||||
|
||||
const filter = {
|
||||
employees: { gt: companyMockInFilter.employees - 1 },
|
||||
};
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[0],
|
||||
record: companyMockInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[1],
|
||||
record: companyMockNotInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('matches a record with a boolean filter on idealCustomerProfile', () => {
|
||||
const filter = { idealCustomerProfile: { eq: true } };
|
||||
const companyIdealCustomerProfileTrue = {
|
||||
...companiesMock[0],
|
||||
idealCustomerProfile: true,
|
||||
};
|
||||
|
||||
const companyIdealCustomerProfileFalse = {
|
||||
...companiesMock[0],
|
||||
idealCustomerProfile: false,
|
||||
};
|
||||
|
||||
const filter = {
|
||||
idealCustomerProfile: {
|
||||
eq: companyIdealCustomerProfileTrue.idealCustomerProfile,
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[0],
|
||||
record: companyIdealCustomerProfileTrue,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true);
|
||||
).toBe(companyIdealCustomerProfileTrue.idealCustomerProfile);
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[4], // Assuming this record has idealCustomerProfile as false
|
||||
record: companyIdealCustomerProfileFalse,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(false);
|
||||
).toBe(companyIdealCustomerProfileFalse.idealCustomerProfile);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Complex And/Or/Not Nesting', () => {
|
||||
it('matches record with a combination of and + or filters', () => {
|
||||
const companyMockInFilter = {
|
||||
...companiesMock[0],
|
||||
idealCustomerProfile: true,
|
||||
employees: 100,
|
||||
};
|
||||
|
||||
const companyMockNotInFilter = {
|
||||
...companiesMock[0],
|
||||
idealCustomerProfile: false,
|
||||
employees: 0,
|
||||
};
|
||||
|
||||
const filter: RecordGqlOperationFilter = {
|
||||
and: [
|
||||
{ domainName: { eq: 'airbnb.com' } },
|
||||
{
|
||||
domainName: {
|
||||
eq: companyMockInFilter.domainName,
|
||||
},
|
||||
},
|
||||
{
|
||||
or: [
|
||||
{ employees: { gt: 10 } },
|
||||
{ idealCustomerProfile: { eq: true } },
|
||||
{
|
||||
employees: {
|
||||
gt: companyMockInFilter.employees - 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
idealCustomerProfile: {
|
||||
eq: companyMockInFilter.idealCustomerProfile,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
@ -175,118 +250,181 @@ describe('isRecordMatchingFilter', () => {
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[0], // Airbnb
|
||||
record: companyMockInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[1], // Aircall
|
||||
record: companyMockNotInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('matches record with nested not filter', () => {
|
||||
const companyMockInFilter = {
|
||||
...companiesMock[0],
|
||||
idealCustomerProfile: true,
|
||||
employees: 100,
|
||||
};
|
||||
|
||||
const companyMockNotInFilter = {
|
||||
...companiesMock[0],
|
||||
idealCustomerProfile: false,
|
||||
name: companyMockInFilter.name + 'Different',
|
||||
};
|
||||
|
||||
const filter: RecordGqlOperationFilter = {
|
||||
not: {
|
||||
and: [
|
||||
{ name: { eq: 'Airbnb' } },
|
||||
{ idealCustomerProfile: { eq: true } },
|
||||
{ name: { eq: companyMockInFilter.name } },
|
||||
{
|
||||
idealCustomerProfile: {
|
||||
eq: companyMockInFilter.idealCustomerProfile,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[0], // Airbnb
|
||||
record: companyMockInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(false); // Should not match as it's Airbnb with idealCustomerProfile true
|
||||
).toBe(false);
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[3], // Apple
|
||||
record: companyMockNotInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true); // Should match as it's not Airbnb
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('matches record with deep nesting of and, or, and not filters', () => {
|
||||
const companyMockInFilter = {
|
||||
...companiesMock[0],
|
||||
idealCustomerProfile: true,
|
||||
employees: 100,
|
||||
};
|
||||
|
||||
const companyMockNotInFilter = {
|
||||
...companiesMock[0],
|
||||
domainName: companyMockInFilter.domainName + 'Different',
|
||||
employees: 5,
|
||||
name: companyMockInFilter.name + 'Different',
|
||||
};
|
||||
|
||||
const filter: RecordGqlOperationFilter = {
|
||||
and: [
|
||||
{ domainName: { eq: 'apple.com' } },
|
||||
{ domainName: { eq: companyMockInFilter.domainName } },
|
||||
{
|
||||
or: [{ employees: { eq: 10 } }, { not: { name: { eq: 'Apple' } } }],
|
||||
or: [
|
||||
{ employees: { eq: companyMockInFilter.employees } },
|
||||
{ not: { name: { eq: companyMockInFilter.name } } },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[3], // Apple
|
||||
record: companyMockInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[4], // Qonto
|
||||
record: companyMockNotInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('matches record with and filter at root level', () => {
|
||||
const companyMockInFilter = {
|
||||
...companiesMock[0],
|
||||
idealCustomerProfile: true,
|
||||
};
|
||||
|
||||
const companyMockNotInFilter = {
|
||||
...companiesMock[0],
|
||||
idealCustomerProfile: false,
|
||||
name: companyMockInFilter.name + 'Different',
|
||||
};
|
||||
|
||||
const filter: RecordGqlOperationFilter = {
|
||||
and: [
|
||||
{ name: { eq: 'Facebook' } },
|
||||
{ idealCustomerProfile: { eq: true } },
|
||||
{ name: { eq: companyMockInFilter.name } },
|
||||
{
|
||||
idealCustomerProfile: {
|
||||
eq: companyMockInFilter.idealCustomerProfile,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[5], // Facebook
|
||||
record: companyMockInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[0], // Airbnb
|
||||
record: companyMockNotInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('matches record with or filter at root level including a not condition', () => {
|
||||
const companyMockInFilter = {
|
||||
...companiesMock[0],
|
||||
idealCustomerProfile: true,
|
||||
employees: 100,
|
||||
};
|
||||
|
||||
const companyMockNotInFilter = {
|
||||
...companiesMock[0],
|
||||
idealCustomerProfile: false,
|
||||
name: companyMockInFilter.name + 'Different',
|
||||
employees: companyMockInFilter.employees - 1,
|
||||
};
|
||||
|
||||
const filter: RecordGqlOperationFilter = {
|
||||
or: [{ name: { eq: 'Sequoia' } }, { not: { employees: { eq: 1 } } }],
|
||||
or: [
|
||||
{ name: { eq: companyMockInFilter.name } },
|
||||
{ not: { employees: { eq: companyMockInFilter.employees - 1 } } },
|
||||
],
|
||||
};
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[6], // Sequoia
|
||||
record: companyMockInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[1], // Aircall
|
||||
record: companyMockNotInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
@ -294,49 +432,75 @@ describe('isRecordMatchingFilter', () => {
|
||||
|
||||
describe('Implicit And Conditions', () => {
|
||||
it('matches record with implicit and of multiple operators within the same field', () => {
|
||||
const companyMockInFilter = {
|
||||
...companiesMock[0],
|
||||
idealCustomerProfile: true,
|
||||
employees: 100,
|
||||
};
|
||||
|
||||
const companyMockNotInFilter = {
|
||||
...companiesMock[0],
|
||||
idealCustomerProfile: false,
|
||||
name: companyMockInFilter.name + 'Different',
|
||||
employees: companyMockInFilter.employees + 100,
|
||||
};
|
||||
|
||||
const filter = {
|
||||
employees: { gt: 10, lt: 100000 },
|
||||
name: { eq: 'Airbnb' },
|
||||
employees: {
|
||||
gt: companyMockInFilter.employees - 10,
|
||||
lt: companyMockInFilter.employees + 10,
|
||||
},
|
||||
name: { eq: companyMockInFilter.name },
|
||||
};
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[0], // Airbnb
|
||||
record: companyMockInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true); // Matches as Airbnb's employee count is between 10 and 100000
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[1], // Aircall
|
||||
record: companyMockNotInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(false); // Does not match as Aircall's employee count is not within the range
|
||||
});
|
||||
|
||||
it('matches record with implicit and within an object passed to or', () => {
|
||||
const companyMockInFilter = {
|
||||
...companiesMock[0],
|
||||
};
|
||||
|
||||
const companyMockNotInFilter = {
|
||||
...companiesMock[0],
|
||||
name: companyMockInFilter.name + 'Different',
|
||||
domainName: companyMockInFilter.name + 'Different',
|
||||
};
|
||||
|
||||
const filter = {
|
||||
or: {
|
||||
name: { eq: 'Airbnb' },
|
||||
domainName: { eq: 'airbnb.com' },
|
||||
name: { eq: companyMockInFilter.name },
|
||||
domainName: { eq: companyMockInFilter.domainName },
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[0], // Airbnb
|
||||
record: companyMockInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
isRecordMatchingFilter({
|
||||
record: mockedCompaniesData[2], // Algolia
|
||||
record: companyMockNotInFilter,
|
||||
filter,
|
||||
objectMetadataItem: mockObjectMetadataItem,
|
||||
objectMetadataItem: companyMockObjectMetadataItem,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
@ -6,10 +6,12 @@ import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorato
|
||||
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 { getCompaniesMock } from '~/testing/mock-data/companies';
|
||||
|
||||
import { RecordDetailDuplicatesSection } from '../RecordDetailDuplicatesSection';
|
||||
|
||||
const companiesMock = getCompaniesMock();
|
||||
|
||||
const meta: Meta<typeof RecordDetailDuplicatesSection> = {
|
||||
title:
|
||||
'Modules/ObjectRecord/RecordShow/RecordDetailSection/RecordDetailDuplicatesSection',
|
||||
@ -21,7 +23,7 @@ const meta: Meta<typeof RecordDetailDuplicatesSection> = {
|
||||
MemoryRouterDecorator,
|
||||
],
|
||||
args: {
|
||||
objectRecordId: mockedCompaniesData[0].id,
|
||||
objectRecordId: companiesMock[0].id,
|
||||
objectNameSingular: CoreObjectNameSingular.Company,
|
||||
},
|
||||
parameters: {
|
||||
|
@ -8,12 +8,16 @@ import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadat
|
||||
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 { getCompaniesMock } from '~/testing/mock-data/companies';
|
||||
import { mockedCompanyObjectMetadataItem } from '~/testing/mock-data/metadata';
|
||||
import { mockedPeopleData } from '~/testing/mock-data/people';
|
||||
import { getPeopleMock } from '~/testing/mock-data/people';
|
||||
|
||||
import { RecordDetailRelationSection } from '../RecordDetailRelationSection';
|
||||
|
||||
const companiesMock = getCompaniesMock();
|
||||
|
||||
const peopleMock = getPeopleMock();
|
||||
|
||||
const meta: Meta<typeof RecordDetailRelationSection> = {
|
||||
title:
|
||||
'Modules/ObjectRecord/RecordShow/RecordDetailSection/RecordDetailRelationSection',
|
||||
@ -22,7 +26,7 @@ const meta: Meta<typeof RecordDetailRelationSection> = {
|
||||
(Story) => (
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
entityId: mockedCompaniesData[0].id,
|
||||
entityId: companiesMock[0].id,
|
||||
basePathToShowPage: '/object-record/',
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: formatFieldMetadataItemAsFieldDefinition({
|
||||
@ -44,7 +48,7 @@ const meta: Meta<typeof RecordDetailRelationSection> = {
|
||||
],
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
records: mockedCompaniesData,
|
||||
records: companiesMock,
|
||||
},
|
||||
};
|
||||
|
||||
@ -58,10 +62,10 @@ export const WithRecords: Story = {
|
||||
parameters: {
|
||||
records: [
|
||||
{
|
||||
...mockedCompaniesData[0],
|
||||
people: mockedPeopleData,
|
||||
...companiesMock[0],
|
||||
people: peopleMock,
|
||||
},
|
||||
...mockedPeopleData,
|
||||
...peopleMock,
|
||||
],
|
||||
},
|
||||
};
|
||||
|
@ -36,13 +36,16 @@ export const useRecordValue = (recordId: string) => {
|
||||
return tableValue?.[recordId] as ObjectRecord | undefined;
|
||||
};
|
||||
|
||||
export const useRecordFieldValue = (recordId: string, fieldName: string) => {
|
||||
export const useRecordFieldValue = <T,>(
|
||||
recordId: string,
|
||||
fieldName: string,
|
||||
) => {
|
||||
const recordFieldValues = useContextSelector(
|
||||
RecordFieldValueSelectorContext,
|
||||
(value) => value[0],
|
||||
);
|
||||
|
||||
return recordFieldValues?.[recordId]?.[fieldName];
|
||||
return recordFieldValues?.[recordId]?.[fieldName] as T;
|
||||
};
|
||||
|
||||
export const useSetRecordFieldValue = () => {
|
||||
|
@ -8,16 +8,18 @@ import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadat
|
||||
import { RelationPickerDecorator } from '~/testing/decorators/RelationPickerDecorator';
|
||||
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedPeopleData } from '~/testing/mock-data/people';
|
||||
import { getPeopleMock } from '~/testing/mock-data/people';
|
||||
import { sleep } from '~/testing/sleep';
|
||||
|
||||
import { EntityForSelect } from '../../types/EntityForSelect';
|
||||
import { SingleEntitySelect } from '../SingleEntitySelect';
|
||||
|
||||
const entities = mockedPeopleData.map<EntityForSelect>((person) => ({
|
||||
const peopleMock = getPeopleMock();
|
||||
|
||||
const entities = peopleMock.map<EntityForSelect>((person) => ({
|
||||
id: person.id,
|
||||
name: person.name.firstName + ' ' + person.name.lastName,
|
||||
avatarUrl: person.avatarUrl,
|
||||
avatarUrl: 'https://picsum.photos/200',
|
||||
avatarType: 'rounded',
|
||||
record: { ...person, __typename: 'Person' },
|
||||
}));
|
||||
|
@ -4,7 +4,7 @@ import { BlocklistItem } from '@/accounts/types/BlocklistItem';
|
||||
import { IconButton } from '@/ui/input/button/components/IconButton';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { formatToHumanReadableDate } from '~/utils';
|
||||
import { formatToHumanReadableDate } from '~/utils/date-utils';
|
||||
|
||||
type SettingsAccountsEmailsBlocklistTableRowProps = {
|
||||
blocklistItem: BlocklistItem;
|
||||
|
@ -4,7 +4,7 @@ import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { mockedBlocklist } from '@/settings/accounts/components/__stories__/mockedBlocklist';
|
||||
import { SettingsAccountsEmailsBlocklistTable } from '@/settings/accounts/components/SettingsAccountsEmailsBlocklistTable';
|
||||
import { formatToHumanReadableDate } from '~/utils';
|
||||
import { formatToHumanReadableDate } from '~/utils/date-utils';
|
||||
|
||||
const handleBlockedEmailRemoveJestFn = fn();
|
||||
|
||||
|
@ -4,7 +4,7 @@ import { ComponentDecorator } from 'twenty-ui';
|
||||
|
||||
import { mockedBlocklist } from '@/settings/accounts/components/__stories__/mockedBlocklist';
|
||||
import { SettingsAccountsEmailsBlocklistTableRow } from '@/settings/accounts/components/SettingsAccountsEmailsBlocklistTableRow';
|
||||
import { formatToHumanReadableDate } from '~/utils';
|
||||
import { formatToHumanReadableDate } from '~/utils/date-utils';
|
||||
|
||||
const onRemoveJestFn = fn();
|
||||
|
||||
|
@ -1,36 +1,32 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { IconCheck, IconX } from 'twenty-ui';
|
||||
import { styled } from '@linaria/react';
|
||||
import { IconCheck, IconX, THEME_COMMON } from 'twenty-ui';
|
||||
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
const spacing = THEME_COMMON.spacingMultiplicator * 1;
|
||||
const iconSizeSm = THEME_COMMON.icon.size.sm;
|
||||
|
||||
const StyledBooleanFieldValue = styled.div`
|
||||
margin-left: ${({ theme }) => theme.spacing(1)};
|
||||
margin-left: ${spacing}px;
|
||||
`;
|
||||
|
||||
type BooleanDisplayProps = {
|
||||
value: boolean | null;
|
||||
value: boolean | null | undefined;
|
||||
};
|
||||
|
||||
export const BooleanDisplay = ({ value }: BooleanDisplayProps) => {
|
||||
const theme = useTheme();
|
||||
if (!isDefined(value)) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const isTrue = value === true;
|
||||
|
||||
return (
|
||||
<>
|
||||
{isDefined(value) ? (
|
||||
<>
|
||||
{value ? (
|
||||
<IconCheck size={theme.icon.size.sm} />
|
||||
) : (
|
||||
<IconX size={theme.icon.size.sm} />
|
||||
)}
|
||||
<StyledBooleanFieldValue>
|
||||
{value ? 'True' : 'False'}
|
||||
</StyledBooleanFieldValue>
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{isTrue ? <IconCheck size={iconSizeSm} /> : <IconX size={iconSizeSm} />}
|
||||
<StyledBooleanFieldValue>
|
||||
{isTrue ? 'True' : 'False'}
|
||||
</StyledBooleanFieldValue>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,20 +1,23 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
import { FieldCurrencyValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { SETTINGS_FIELD_CURRENCY_CODES } from '@/settings/data-model/constants/SettingsFieldCurrencyCodes';
|
||||
import { formatAmount } from '~/utils/format/formatAmount';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { EllipsisDisplay } from './EllipsisDisplay';
|
||||
|
||||
type CurrencyDisplayProps = {
|
||||
currencyValue: FieldCurrencyValue | null | undefined;
|
||||
};
|
||||
|
||||
const StyledEllipsisDisplay = styled(EllipsisDisplay)`
|
||||
const StyledEllipsisDisplay = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const CurrencyDisplay = ({ currencyValue }: CurrencyDisplayProps) => {
|
||||
@ -26,9 +29,7 @@ export const CurrencyDisplay = ({ currencyValue }: CurrencyDisplayProps) => {
|
||||
? SETTINGS_FIELD_CURRENCY_CODES[currencyValue?.currencyCode]?.Icon
|
||||
: null;
|
||||
|
||||
const amountToDisplay = isDefined(currencyValue?.amountMicros)
|
||||
? currencyValue.amountMicros / 1000000
|
||||
: 0;
|
||||
const amountToDisplay = (currencyValue?.amountMicros ?? 0) / 1000000;
|
||||
|
||||
if (!shouldDisplayCurrency) {
|
||||
return <StyledEllipsisDisplay>{0}</StyledEllipsisDisplay>;
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { formatToHumanReadableDate } from '~/utils';
|
||||
import { formatISOStringToHumanReadableDate } from '~/utils/date-utils';
|
||||
|
||||
import { EllipsisDisplay } from './EllipsisDisplay';
|
||||
|
||||
type DateDisplayProps = {
|
||||
value: Date | string | null | undefined;
|
||||
value: string | null | undefined;
|
||||
};
|
||||
|
||||
export const DateDisplay = ({ value }: DateDisplayProps) => (
|
||||
<EllipsisDisplay>{value && formatToHumanReadableDate(value)}</EllipsisDisplay>
|
||||
<EllipsisDisplay>
|
||||
{value ? formatISOStringToHumanReadableDate(value) : ''}
|
||||
</EllipsisDisplay>
|
||||
);
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { formatToHumanReadableDateTime } from '~/utils';
|
||||
import { formatISOStringToHumanReadableDateTime } from '~/utils/date-utils';
|
||||
|
||||
import { EllipsisDisplay } from './EllipsisDisplay';
|
||||
|
||||
type DateTimeDisplayProps = {
|
||||
value: Date | string | null | undefined;
|
||||
value: string | null | undefined;
|
||||
};
|
||||
|
||||
export const DateTimeDisplay = ({ value }: DateTimeDisplayProps) => (
|
||||
<EllipsisDisplay>
|
||||
{value && formatToHumanReadableDateTime(value)}
|
||||
{value ? formatISOStringToHumanReadableDateTime(value) : ''}
|
||||
</EllipsisDisplay>
|
||||
);
|
||||
|
@ -1,21 +1,39 @@
|
||||
import { MouseEvent } from 'react';
|
||||
|
||||
import { ContactLink } from '@/ui/navigation/link/components/ContactLink';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { EllipsisDisplay } from './EllipsisDisplay';
|
||||
|
||||
const validateEmail = (email: string) => {
|
||||
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailPattern.test(email.trim());
|
||||
// const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
// return emailPattern.test(email.trim());
|
||||
|
||||
// Record this without using regex
|
||||
const emailParts = email.split('@');
|
||||
|
||||
if (emailParts.length !== 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
type EmailDisplayProps = {
|
||||
value: string | null;
|
||||
};
|
||||
|
||||
export const EmailDisplay = ({ value }: EmailDisplayProps) => (
|
||||
<EllipsisDisplay>
|
||||
{value && validateEmail(value) ? (
|
||||
export const EmailDisplay = ({ value }: EmailDisplayProps) => {
|
||||
if (!isDefined(value)) {
|
||||
return <ContactLink href="#">{value}</ContactLink>;
|
||||
}
|
||||
|
||||
if (!validateEmail(value)) {
|
||||
return <ContactLink href="#">{value}</ContactLink>;
|
||||
}
|
||||
|
||||
return (
|
||||
<EllipsisDisplay>
|
||||
<ContactLink
|
||||
href={`mailto:${value}`}
|
||||
onClick={(event: MouseEvent<HTMLElement>) => {
|
||||
@ -24,8 +42,6 @@ export const EmailDisplay = ({ value }: EmailDisplayProps) => (
|
||||
>
|
||||
{value}
|
||||
</ContactLink>
|
||||
) : (
|
||||
<ContactLink href="#">{value}</ContactLink>
|
||||
)}
|
||||
</EllipsisDisplay>
|
||||
);
|
||||
</EllipsisDisplay>
|
||||
);
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { MouseEvent } from 'react';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { FieldLinkValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { RoundedLink } from '@/ui/navigation/link/components/RoundedLink';
|
||||
@ -6,34 +6,31 @@ import {
|
||||
LinkType,
|
||||
SocialLink,
|
||||
} from '@/ui/navigation/link/components/SocialLink';
|
||||
import { checkUrlType } from '~/utils/checkUrlType';
|
||||
import { getAbsoluteUrl } from '~/utils/url/getAbsoluteUrl';
|
||||
import { getUrlHostName } from '~/utils/url/getUrlHostName';
|
||||
|
||||
type LinkDisplayProps = {
|
||||
value?: FieldLinkValue;
|
||||
};
|
||||
|
||||
export const LinkDisplay = ({ value }: LinkDisplayProps) => {
|
||||
const handleClick = (event: MouseEvent<HTMLElement>) => {
|
||||
event.stopPropagation();
|
||||
};
|
||||
const url = value?.url;
|
||||
|
||||
const absoluteUrl = getAbsoluteUrl(value?.url || '');
|
||||
const displayedValue = value?.label || getUrlHostName(absoluteUrl);
|
||||
const type = checkUrlType(absoluteUrl);
|
||||
|
||||
if (type === LinkType.LinkedIn || type === LinkType.Twitter) {
|
||||
return (
|
||||
<SocialLink href={absoluteUrl} onClick={handleClick} type={type}>
|
||||
{displayedValue}
|
||||
</SocialLink>
|
||||
);
|
||||
if (!isNonEmptyString(url)) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<RoundedLink href={absoluteUrl} onClick={handleClick}>
|
||||
{displayedValue}
|
||||
</RoundedLink>
|
||||
);
|
||||
const displayedValue = isNonEmptyString(value?.label)
|
||||
? value?.label
|
||||
: url?.replace(/^http[s]?:\/\/(?:[w]+\.)?/gm, '').replace(/^[w]+\./gm, '');
|
||||
|
||||
const type = displayedValue.startsWith('linkedin.')
|
||||
? LinkType.LinkedIn
|
||||
: displayedValue.startsWith('twitter.')
|
||||
? LinkType.Twitter
|
||||
: LinkType.Url;
|
||||
|
||||
if (type === LinkType.LinkedIn || type === LinkType.Twitter) {
|
||||
return <SocialLink href={url} type={type} label={displayedValue} />;
|
||||
}
|
||||
|
||||
return <RoundedLink href={url} label={displayedValue} />;
|
||||
};
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { MouseEventHandler, useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { styled } from '@linaria/react';
|
||||
import { THEME_COMMON } from 'twenty-ui';
|
||||
|
||||
import { FieldLinksValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { ExpandableList } from '@/ui/layout/expandable-list/components/ExpandableList';
|
||||
@ -17,6 +19,21 @@ type LinksDisplayProps = {
|
||||
isFocused?: boolean;
|
||||
};
|
||||
|
||||
const themeSpacing = THEME_COMMON.spacingMultiplicator;
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${themeSpacing * 1}px;
|
||||
justify-content: flex-start;
|
||||
|
||||
max-width: 100%;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const LinksDisplay = ({ value, isFocused }: LinksDisplayProps) => {
|
||||
const links = useMemo(
|
||||
() =>
|
||||
@ -41,21 +58,25 @@ export const LinksDisplay = ({ value, isFocused }: LinksDisplayProps) => {
|
||||
[value?.primaryLinkLabel, value?.primaryLinkUrl, value?.secondaryLinks],
|
||||
);
|
||||
|
||||
const handleClick: MouseEventHandler = (event) => event.stopPropagation();
|
||||
|
||||
return (
|
||||
return isFocused ? (
|
||||
<ExpandableList isChipCountDisplayed={isFocused}>
|
||||
{links.map(({ url, label, type }, index) =>
|
||||
type === LinkType.LinkedIn || type === LinkType.Twitter ? (
|
||||
<SocialLink key={index} href={url} onClick={handleClick} type={type}>
|
||||
{label}
|
||||
</SocialLink>
|
||||
<SocialLink key={index} href={url} type={type} label={label} />
|
||||
) : (
|
||||
<RoundedLink key={index} href={url} onClick={handleClick}>
|
||||
{label}
|
||||
</RoundedLink>
|
||||
<RoundedLink key={index} href={url} label={label} />
|
||||
),
|
||||
)}
|
||||
</ExpandableList>
|
||||
) : (
|
||||
<StyledContainer>
|
||||
{links.map(({ url, label, type }, index) =>
|
||||
type === LinkType.LinkedIn || type === LinkType.Twitter ? (
|
||||
<SocialLink key={index} href={url} type={type} label={label} />
|
||||
) : (
|
||||
<RoundedLink key={index} href={url} label={label} />
|
||||
),
|
||||
)}
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
@ -42,17 +42,22 @@ export const URLDisplay = ({ value }: URLDisplayProps) => {
|
||||
if (type === LinkType.LinkedIn || type === LinkType.Twitter) {
|
||||
return (
|
||||
<EllipsisDisplay>
|
||||
<SocialLink href={absoluteUrl} onClick={handleClick} type={type}>
|
||||
{displayedValue}
|
||||
</SocialLink>
|
||||
<SocialLink
|
||||
href={absoluteUrl}
|
||||
onClick={handleClick}
|
||||
type={type}
|
||||
label={displayedValue}
|
||||
/>
|
||||
</EllipsisDisplay>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<EllipsisDisplay>
|
||||
<StyledRawLink href={absoluteUrl} onClick={handleClick}>
|
||||
{displayedValue}
|
||||
</StyledRawLink>
|
||||
<StyledRawLink
|
||||
href={absoluteUrl}
|
||||
onClick={handleClick}
|
||||
label={displayedValue}
|
||||
/>
|
||||
</EllipsisDisplay>
|
||||
);
|
||||
};
|
||||
|
@ -1,50 +1,71 @@
|
||||
import * as React from 'react';
|
||||
import { Link as ReactLink } from 'react-router-dom';
|
||||
import styled from '@emotion/styled';
|
||||
import { Chip, ChipSize, ChipVariant } from 'twenty-ui';
|
||||
import { MouseEvent } from 'react';
|
||||
import { styled } from '@linaria/react';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { FONT_COMMON, THEME_COMMON } from 'twenty-ui';
|
||||
|
||||
type RoundedLinkProps = {
|
||||
href: string;
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
label?: string;
|
||||
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
|
||||
};
|
||||
|
||||
const StyledLink = styled(ReactLink)`
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
const fontSizeMd = FONT_COMMON.size.md;
|
||||
const spacing1 = THEME_COMMON.spacing(1);
|
||||
const spacing3 = THEME_COMMON.spacing(3);
|
||||
|
||||
const spacingMultiplicator = THEME_COMMON.spacingMultiplicator;
|
||||
|
||||
const StyledLink = styled.a`
|
||||
align-items: center;
|
||||
background-color: var(--twentycrm-background-transparent-light);
|
||||
border: 1px solid var(--twentycrm-border-color-medium);
|
||||
|
||||
border-radius: 50px;
|
||||
color: var(--twentycrm-font-color-primary);
|
||||
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
font-weight: ${fontSizeMd};
|
||||
|
||||
gap: ${spacing1};
|
||||
|
||||
height: ${spacing3};
|
||||
justify-content: center;
|
||||
|
||||
max-width: calc(100% - ${spacingMultiplicator} * 2px);
|
||||
|
||||
max-width: 100%;
|
||||
height: ${({ theme }) => theme.spacing(5)};
|
||||
|
||||
min-width: fit-content;
|
||||
|
||||
overflow: hidden;
|
||||
padding: ${spacing1} ${spacing1};
|
||||
|
||||
text-decoration: none;
|
||||
text-overflow: ellipsis;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
const StyledChip = styled(Chip)`
|
||||
border-color: ${({ theme }) => theme.border.color.strong};
|
||||
box-sizing: border-box;
|
||||
padding: ${({ theme }) => theme.spacing(0, 2)};
|
||||
max-width: 100%;
|
||||
height: ${({ theme }) => theme.spacing(5)};
|
||||
min-width: 40px;
|
||||
`;
|
||||
export const RoundedLink = ({ label, href, onClick }: RoundedLinkProps) => {
|
||||
if (!isNonEmptyString(label)) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
export const RoundedLink = ({
|
||||
children,
|
||||
className,
|
||||
href,
|
||||
onClick,
|
||||
}: RoundedLinkProps) => {
|
||||
if (!children) return null;
|
||||
const handleClick = (event: MouseEvent<HTMLElement>) => {
|
||||
event.stopPropagation();
|
||||
|
||||
onClick?.(event);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledLink
|
||||
className={className}
|
||||
href={href}
|
||||
target="_blank"
|
||||
to={href}
|
||||
onClick={onClick}
|
||||
rel="noreferrer"
|
||||
onClick={handleClick}
|
||||
>
|
||||
<StyledChip
|
||||
label={`${children}`}
|
||||
variant={ChipVariant.Rounded}
|
||||
size={ChipSize.Large}
|
||||
/>
|
||||
{label}
|
||||
</StyledLink>
|
||||
);
|
||||
};
|
||||
|
@ -11,24 +11,15 @@ export enum LinkType {
|
||||
}
|
||||
|
||||
type SocialLinkProps = {
|
||||
label: string;
|
||||
href: string;
|
||||
children?: React.ReactNode;
|
||||
type: LinkType;
|
||||
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
|
||||
};
|
||||
|
||||
export const SocialLink = ({
|
||||
children,
|
||||
href,
|
||||
onClick,
|
||||
type,
|
||||
}: SocialLinkProps) => {
|
||||
export const SocialLink = ({ label, href, onClick, type }: SocialLinkProps) => {
|
||||
const displayValue =
|
||||
getDisplayValueByUrlType({ type: type, href: href }) ?? children;
|
||||
getDisplayValueByUrlType({ type: type, href: href }) ?? label;
|
||||
|
||||
return (
|
||||
<RoundedLink href={href} onClick={onClick}>
|
||||
{displayValue}
|
||||
</RoundedLink>
|
||||
);
|
||||
return <RoundedLink href={href} onClick={onClick} label={displayValue} />;
|
||||
};
|
||||
|
@ -11,7 +11,7 @@ const meta: Meta<typeof RoundedLink> = {
|
||||
decorators: [ComponentWithRouterDecorator],
|
||||
args: {
|
||||
href: '/test',
|
||||
children: 'Rounded chip',
|
||||
label: 'Rounded chip',
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -11,7 +11,7 @@ const meta: Meta<typeof SocialLink> = {
|
||||
decorators: [ComponentWithRouterDecorator],
|
||||
args: {
|
||||
href: '/test',
|
||||
children: 'Social Link',
|
||||
label: 'Social Link',
|
||||
},
|
||||
};
|
||||
|
||||
@ -25,7 +25,7 @@ const twitter: LinkType = LinkType.Twitter;
|
||||
export const LinkedIn: Story = {
|
||||
args: {
|
||||
href: '/LinkedIn',
|
||||
children: 'LinkedIn',
|
||||
label: 'LinkedIn',
|
||||
onClick: clickJestFn,
|
||||
type: linkedin,
|
||||
},
|
||||
@ -34,7 +34,7 @@ export const LinkedIn: Story = {
|
||||
export const Twitter: Story = {
|
||||
args: {
|
||||
href: '/Twitter',
|
||||
children: 'Twitter',
|
||||
label: 'Twitter',
|
||||
onClick: clickJestFn,
|
||||
type: twitter,
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useEffect } from 'react';
|
||||
import { ThemeProvider } from '@emotion/react';
|
||||
import { THEME_DARK, THEME_LIGHT } from 'twenty-ui';
|
||||
import { THEME_DARK, THEME_LIGHT, ThemeContextProvider } from 'twenty-ui';
|
||||
|
||||
import { useColorScheme } from '../hooks/useColorScheme';
|
||||
import { useSystemColorScheme } from '../hooks/useSystemColorScheme';
|
||||
@ -24,5 +24,9 @@ export const AppThemeProvider = ({ children }: AppThemeProviderProps) => {
|
||||
theme.name === 'dark' ? 'dark' : 'light';
|
||||
}, [theme]);
|
||||
|
||||
return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<ThemeContextProvider theme={theme}>{children}</ThemeContextProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
@ -35,7 +35,6 @@ export const Default: Story = {
|
||||
await canvas.findByText('People');
|
||||
await canvas.findAllByText('Companies');
|
||||
await canvas.findByText('Opportunities');
|
||||
await canvas.findByText('Listings');
|
||||
await canvas.findByText('My Customs');
|
||||
},
|
||||
};
|
||||
|
@ -8,11 +8,13 @@ import {
|
||||
PageDecoratorArgs,
|
||||
} from '~/testing/decorators/PageDecorator';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedPeopleData } from '~/testing/mock-data/people';
|
||||
import { getPeopleMock } from '~/testing/mock-data/people';
|
||||
import { mockedWorkspaceMemberData } from '~/testing/mock-data/users';
|
||||
|
||||
import { RecordShowPage } from '../RecordShowPage';
|
||||
|
||||
const peopleMock = getPeopleMock();
|
||||
|
||||
const meta: Meta<PageDecoratorArgs> = {
|
||||
title: 'Pages/ObjectRecord/RecordShowPage',
|
||||
component: RecordShowPage,
|
||||
@ -29,7 +31,7 @@ const meta: Meta<PageDecoratorArgs> = {
|
||||
graphql.query('FindOnePerson', () => {
|
||||
return HttpResponse.json({
|
||||
data: {
|
||||
person: mockedPeopleData[0],
|
||||
person: peopleMock[0],
|
||||
},
|
||||
});
|
||||
}),
|
||||
@ -87,7 +89,9 @@ export const Default: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
await canvas.findAllByText('Alexandre Prot');
|
||||
await canvas.findAllByText(
|
||||
peopleMock[0].name.firstName + ' ' + peopleMock[0].name.lastName,
|
||||
);
|
||||
await canvas.findByText('Add your first Activity');
|
||||
},
|
||||
};
|
||||
@ -99,7 +103,11 @@ export const Loading: Story = {
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
expect(canvas.queryByText('Alexandre Prot')).toBeNull();
|
||||
expect(
|
||||
canvas.queryByText(
|
||||
peopleMock[0].name.firstName + ' ' + peopleMock[0].name.lastName,
|
||||
),
|
||||
).toBeNull();
|
||||
expect(canvas.queryByText('Add your first Activity')).toBeNull();
|
||||
},
|
||||
};
|
||||
|
@ -161,13 +161,13 @@ export const SettingsObjectNewFieldStep2 = () => {
|
||||
|
||||
const excludedFieldTypes: SettingsSupportedFieldType[] = (
|
||||
[
|
||||
FieldMetadataType.Email,
|
||||
FieldMetadataType.FullName,
|
||||
FieldMetadataType.Link,
|
||||
// FieldMetadataType.Email,
|
||||
// FieldMetadataType.FullName,
|
||||
// FieldMetadataType.Link,
|
||||
FieldMetadataType.Numeric,
|
||||
FieldMetadataType.Probability,
|
||||
FieldMetadataType.Uuid,
|
||||
FieldMetadataType.Phone,
|
||||
// FieldMetadataType.Uuid,
|
||||
// FieldMetadataType.Phone,
|
||||
] as const
|
||||
).filter(isDefined);
|
||||
|
||||
|
@ -2,7 +2,6 @@ import { useEffect } from 'react';
|
||||
import { Decorator } from '@storybook/react';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { Company } from '@/companies/types/Company';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
@ -12,18 +11,17 @@ import {
|
||||
} from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { Person } from '@/people/types/Person';
|
||||
import { mockedCompaniesDataV2 } from '~/testing/mock-data/companiesV2';
|
||||
import { getCompaniesMock } from '~/testing/mock-data/companies';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/mock-data/objectMetadataItems';
|
||||
import { mockPeopleDataV2 } from '~/testing/mock-data/peopleV2';
|
||||
import { getPeopleMock } from '~/testing/mock-data/people';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
const RecordMockSetterEffect = ({
|
||||
companies,
|
||||
people,
|
||||
}: {
|
||||
companies: Company[];
|
||||
people: Person[];
|
||||
companies: ObjectRecord[];
|
||||
people: ObjectRecord[];
|
||||
}) => {
|
||||
const setRecordValue = useSetRecordValue();
|
||||
|
||||
@ -56,21 +54,25 @@ export const getFieldDecorator =
|
||||
fieldValue?: any,
|
||||
): Decorator =>
|
||||
(Story) => {
|
||||
const companiesMock = getCompaniesMock();
|
||||
|
||||
const companies =
|
||||
objectNameSingular === 'company' && isDefined(fieldValue)
|
||||
? [
|
||||
{ ...mockedCompaniesDataV2[0], [fieldName]: fieldValue },
|
||||
...mockedCompaniesDataV2.slice(1),
|
||||
{ ...companiesMock[0], [fieldName]: fieldValue },
|
||||
...companiesMock.slice(1),
|
||||
]
|
||||
: mockedCompaniesDataV2;
|
||||
: companiesMock;
|
||||
|
||||
const peopleMock = getPeopleMock();
|
||||
|
||||
const people =
|
||||
objectNameSingular === 'person' && isDefined(fieldValue)
|
||||
? [
|
||||
{ ...mockPeopleDataV2[0], [fieldName]: fieldValue },
|
||||
...mockPeopleDataV2.slice(1),
|
||||
{ ...peopleMock[0], [fieldName]: fieldValue },
|
||||
...peopleMock.slice(1),
|
||||
]
|
||||
: mockPeopleDataV2;
|
||||
: peopleMock;
|
||||
|
||||
const record = objectNameSingular === 'company' ? companies[0] : people[0];
|
||||
|
||||
|
@ -8,20 +8,24 @@ import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||
import { mockedActivities } from '~/testing/mock-data/activities';
|
||||
import {
|
||||
mockedCompaniesData,
|
||||
mockedDuplicateCompanyData,
|
||||
getCompaniesMock,
|
||||
getCompanyDuplicateMock,
|
||||
} from '~/testing/mock-data/companies';
|
||||
import { mockedClientConfig } from '~/testing/mock-data/config';
|
||||
import { mockedObjectMetadataItemsQueryResult } from '~/testing/mock-data/metadata';
|
||||
import { getPeopleMock } from '~/testing/mock-data/people';
|
||||
import { mockedRemoteTables } from '~/testing/mock-data/remote-tables';
|
||||
import { mockedUsersData } from '~/testing/mock-data/users';
|
||||
import { mockedViewsData } from '~/testing/mock-data/views';
|
||||
import { mockWorkspaceMembers } from '~/testing/mock-data/workspace-members';
|
||||
|
||||
import { mockedPeopleData } from './mock-data/people';
|
||||
import { mockedRemoteServers } from './mock-data/remote-servers';
|
||||
import { mockedViewFieldsData } from './mock-data/view-fields';
|
||||
|
||||
const peopleMock = getPeopleMock();
|
||||
const companiesMock = getCompaniesMock();
|
||||
const duplicateCompanyMock = getCompanyDuplicateMock();
|
||||
|
||||
export const metadataGraphql = graphql.link(
|
||||
`${REACT_APP_SERVER_BASE_URL}/metadata`,
|
||||
);
|
||||
@ -108,8 +112,8 @@ export const graphqlMocks = {
|
||||
}),
|
||||
graphql.query('FindManyCompanies', ({ variables }) => {
|
||||
const mockedData = variables.limit
|
||||
? mockedCompaniesData.slice(0, variables.limit)
|
||||
: mockedCompaniesData;
|
||||
? companiesMock.slice(0, variables.limit)
|
||||
: companiesMock;
|
||||
|
||||
return HttpResponse.json({
|
||||
data: {
|
||||
@ -157,7 +161,7 @@ export const graphqlMocks = {
|
||||
edges: [
|
||||
{
|
||||
node: {
|
||||
...mockedDuplicateCompanyData,
|
||||
...duplicateCompanyMock,
|
||||
favorites: {
|
||||
edges: [],
|
||||
__typename: 'FavoriteConnection',
|
||||
@ -197,7 +201,7 @@ export const graphqlMocks = {
|
||||
return HttpResponse.json({
|
||||
data: {
|
||||
people: {
|
||||
edges: mockedPeopleData.map((person) => ({
|
||||
edges: peopleMock.map((person) => ({
|
||||
node: person,
|
||||
cursor: null,
|
||||
})),
|
||||
|
@ -1,210 +1,393 @@
|
||||
import { Company } from '@/companies/types/Company';
|
||||
import { Favorite } from '@/favorites/types/Favorite';
|
||||
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
|
||||
|
||||
import { mockedUsersData } from './users';
|
||||
|
||||
type MockedCompany = Omit<Company, 'deletedAt'> & {
|
||||
accountOwner: WorkspaceMember | null;
|
||||
Favorite: Pick<Favorite, 'id'> | null;
|
||||
export const getCompaniesMock = () => {
|
||||
return companiesQueryResult.companies.edges.map((edge) => edge.node);
|
||||
};
|
||||
|
||||
export const mockedCompaniesData: Array<MockedCompany> = [
|
||||
{
|
||||
export const getCompanyDuplicateMock = () => {
|
||||
return {
|
||||
...companiesQueryResult.companies.edges[0].node,
|
||||
id: '8b40856a-2ec9-4c03-8bc0-c032c89e1824',
|
||||
};
|
||||
};
|
||||
|
||||
export const getEmptyCompanyMock = () => {
|
||||
return {
|
||||
id: '9231e6ee-4cc2-4c7b-8c55-dff16f4d968a',
|
||||
name: '',
|
||||
domainName: '',
|
||||
address: '',
|
||||
accountOwner: null,
|
||||
createdAt: null,
|
||||
updatedAt: null,
|
||||
employees: null,
|
||||
idealCustomerProfile: null,
|
||||
linkedinLink: null,
|
||||
xLink: null,
|
||||
_activityCount: null,
|
||||
__typename: 'Company',
|
||||
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
||||
domainName: 'airbnb.com',
|
||||
name: 'Airbnb',
|
||||
createdAt: '2023-04-26T10:08:54.724515+00:00',
|
||||
updatedAt: '2023-04-26T10:23:42.33625+00:00',
|
||||
address: '17 rue de clignancourt',
|
||||
employees: 12,
|
||||
linkedinLink: {
|
||||
url: 'https://www.linkedin.com/company/airbnb/',
|
||||
label: 'https://www.linkedin.com/company/airbnb/',
|
||||
};
|
||||
};
|
||||
|
||||
export const companiesQueryResult = {
|
||||
companies: {
|
||||
__typename: 'CompanyConnection',
|
||||
totalCount: 13,
|
||||
pageInfo: {
|
||||
__typename: 'PageInfo',
|
||||
hasNextPage: false,
|
||||
startCursor:
|
||||
'WzEsICIyMDIwMjAyMC0zZWMzLTRmZTMtODk5Ny1iNzZhYTBiZmE0MDgiXQ==',
|
||||
endCursor: 'WzEzLCAiMjAyMDIwMjAtMTQ1NS00YzU3LWFmYWYtZGQ1ZGMwODYzNjFkIl0=',
|
||||
},
|
||||
xLink: {
|
||||
url: 'https://twitter.com/airbnb',
|
||||
label: 'https://twitter.com/airbnb',
|
||||
},
|
||||
annualRecurringRevenue: { amountMicros: 5000000, currencyCode: 'USD' },
|
||||
idealCustomerProfile: true,
|
||||
Favorite: null,
|
||||
accountOwnerId: mockedUsersData[0].id,
|
||||
accountOwner: {
|
||||
__typename: 'WorkspaceMember',
|
||||
name: {
|
||||
firstName: 'Charles',
|
||||
lastName: 'Test',
|
||||
edges: [
|
||||
{
|
||||
__typename: 'CompanyEdge',
|
||||
cursor: 'WzEsICIyMDIwMjAyMC0zZWMzLTRmZTMtODk5Ny1iNzZhYTBiZmE0MDgiXQ==',
|
||||
node: {
|
||||
__typename: 'Company',
|
||||
id: '20202020-3ec3-4fe3-8997-b76aa0bfa408',
|
||||
employees: 100,
|
||||
createdAt: '2024-06-05T09:00:20.412Z',
|
||||
name: 'Linkedin',
|
||||
accountOwner: null,
|
||||
domainName: 'linkedin.com',
|
||||
address: '',
|
||||
position: 1,
|
||||
idealCustomerProfile: true,
|
||||
linkedinLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: '',
|
||||
},
|
||||
previousEmployees: {
|
||||
__typename: 'Person',
|
||||
id: '20202020-2d40-4e49-8df4-9c6a049191de',
|
||||
email: 'louis.duss@google.com',
|
||||
position: 14,
|
||||
testJson: null,
|
||||
testRating: null,
|
||||
companyId: '20202020-c21e-4ec2-873b-de4264d89025',
|
||||
avatarUrl: '',
|
||||
updatedAt: '2024-06-05T09:36:42.400Z',
|
||||
testMultiSelect: null,
|
||||
testBoolean: true,
|
||||
testSelect: 'OPTION_1',
|
||||
testDateOnly: null,
|
||||
bestCompanyId: null,
|
||||
testUuid: null,
|
||||
phone: '+33788901234',
|
||||
createdAt: '2024-06-05T09:00:20.412Z',
|
||||
city: 'Seattle',
|
||||
testPhone: '',
|
||||
jobTitle: 'CTO',
|
||||
testCurrency: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: 'USD',
|
||||
},
|
||||
xLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: 'twitter.com',
|
||||
},
|
||||
testLinks: {
|
||||
__typename: 'Links',
|
||||
primaryLinkUrl: '',
|
||||
primaryLinkLabel: '',
|
||||
secondaryLinks: null,
|
||||
},
|
||||
name: {
|
||||
__typename: 'FullName',
|
||||
firstName: 'Louis',
|
||||
lastName: 'Duss',
|
||||
},
|
||||
linkedinLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: 'linkedin.com',
|
||||
},
|
||||
testAddress: {
|
||||
__typename: 'Address',
|
||||
addressStreet1: '',
|
||||
addressStreet2: '',
|
||||
addressCity: '',
|
||||
addressState: '',
|
||||
addressCountry: '',
|
||||
addressPostcode: '',
|
||||
addressLat: null,
|
||||
addressLng: null,
|
||||
},
|
||||
testLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
avatarUrl: null,
|
||||
id: mockedUsersData[0].id,
|
||||
locale: 'en',
|
||||
colorScheme: 'Light',
|
||||
updatedAt: '2023-04-26T10:23:42.33625+00:00',
|
||||
createdAt: '2023-04-26T10:23:42.33625+00:00',
|
||||
userId: mockedUsersData[0].id,
|
||||
userEmail: 'charles@test.com',
|
||||
},
|
||||
{
|
||||
__typename: 'CompanyEdge',
|
||||
cursor: 'WzIsICIyMDIwMjAyMC01ZDgxLTQ2ZDYtYmY4My1mN2ZkMzNlYTYxMDIiXQ==',
|
||||
node: {
|
||||
__typename: 'Company',
|
||||
id: '20202020-5d81-46d6-bf83-f7fd33ea6102',
|
||||
employees: null,
|
||||
createdAt: '2024-06-05T09:00:20.412Z',
|
||||
name: 'Facebook',
|
||||
idealCustomerProfile: false,
|
||||
accountOwner: null,
|
||||
domainName: 'facebook.com',
|
||||
address: '',
|
||||
previousEmployees: null,
|
||||
position: 2,
|
||||
linkedinLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'CompanyEdge',
|
||||
cursor: 'WzMsICIyMDIwMjAyMC0wNzEzLTQwYTUtODIxNi04MjgwMjQwMWQzM2UiXQ==',
|
||||
node: {
|
||||
__typename: 'Company',
|
||||
id: '20202020-0713-40a5-8216-82802401d33e',
|
||||
employees: null,
|
||||
createdAt: '2024-06-05T09:00:20.412Z',
|
||||
name: 'Qonto',
|
||||
idealCustomerProfile: false,
|
||||
accountOwner: null,
|
||||
domainName: 'qonto.com',
|
||||
address: '',
|
||||
previousEmployees: null,
|
||||
position: 3,
|
||||
linkedinLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'CompanyEdge',
|
||||
cursor: 'WzQsICIyMDIwMjAyMC1lZDg5LTQxM2EtYjMxYS05NjI5ODZlNjdiYjQiXQ==',
|
||||
node: {
|
||||
__typename: 'Company',
|
||||
id: '20202020-ed89-413a-b31a-962986e67bb4',
|
||||
employees: null,
|
||||
createdAt: '2024-06-05T09:00:20.412Z',
|
||||
name: 'Microsoft',
|
||||
idealCustomerProfile: true,
|
||||
accountOwner: null,
|
||||
domainName: 'microsoft.com',
|
||||
address: '',
|
||||
previousEmployees: null,
|
||||
position: 4,
|
||||
linkedinLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'CompanyEdge',
|
||||
cursor: 'WzUsICIyMDIwMjAyMC0xNzFlLTRiY2MtOWNmNy00MzQ0OGQ2ZmIyNzgiXQ==',
|
||||
node: {
|
||||
__typename: 'Company',
|
||||
id: '20202020-171e-4bcc-9cf7-43448d6fb278',
|
||||
employees: null,
|
||||
createdAt: '2024-06-05T09:00:20.412Z',
|
||||
name: 'Airbnb',
|
||||
idealCustomerProfile: true,
|
||||
accountOwner: null,
|
||||
domainName: 'airbnb.com',
|
||||
address: '',
|
||||
previousEmployees: null,
|
||||
position: 5,
|
||||
linkedinLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'CompanyEdge',
|
||||
cursor: 'WzYsICIyMDIwMjAyMC1jMjFlLTRlYzItODczYi1kZTQyNjRkODkwMjUiXQ==',
|
||||
node: {
|
||||
__typename: 'Company',
|
||||
id: '20202020-c21e-4ec2-873b-de4264d89025',
|
||||
employees: null,
|
||||
createdAt: '2024-06-05T09:00:20.412Z',
|
||||
name: 'Google',
|
||||
idealCustomerProfile: false,
|
||||
accountOwner: null,
|
||||
domainName: 'google.com',
|
||||
address: '',
|
||||
previousEmployees: null,
|
||||
position: 6,
|
||||
linkedinLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'CompanyEdge',
|
||||
cursor: 'WzcsICIyMDIwMjAyMC03MDdlLTQ0ZGMtYTFkMi0zMDAzMGJmMWE5NDQiXQ==',
|
||||
node: {
|
||||
__typename: 'Company',
|
||||
id: '20202020-707e-44dc-a1d2-30030bf1a944',
|
||||
employees: null,
|
||||
createdAt: '2024-06-05T09:00:20.412Z',
|
||||
name: 'Netflix',
|
||||
idealCustomerProfile: true,
|
||||
accountOwner: null,
|
||||
domainName: 'netflix.com',
|
||||
address: '',
|
||||
previousEmployees: null,
|
||||
position: 7,
|
||||
linkedinLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'CompanyEdge',
|
||||
cursor: 'WzgsICIyMDIwMjAyMC0zZjc0LTQ5MmQtYTEwMS0yYTcwZjUwYTE2NDUiXQ==',
|
||||
node: {
|
||||
__typename: 'Company',
|
||||
id: '20202020-3f74-492d-a101-2a70f50a1645',
|
||||
employees: null,
|
||||
createdAt: '2024-06-05T09:00:20.412Z',
|
||||
name: 'Libeo',
|
||||
idealCustomerProfile: false,
|
||||
accountOwner: null,
|
||||
domainName: 'libeo.io',
|
||||
address: '',
|
||||
previousEmployees: null,
|
||||
position: 8,
|
||||
linkedinLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'CompanyEdge',
|
||||
cursor: 'WzksICIyMDIwMjAyMC1jZmJmLTQxNTYtYTc5MC1lMzk4NTRkY2Q0ZWIiXQ==',
|
||||
node: {
|
||||
__typename: 'Company',
|
||||
id: '20202020-cfbf-4156-a790-e39854dcd4eb',
|
||||
employees: null,
|
||||
createdAt: '2024-06-05T09:00:20.412Z',
|
||||
name: 'Claap',
|
||||
idealCustomerProfile: false,
|
||||
accountOwner: null,
|
||||
domainName: 'claap.io',
|
||||
address: '',
|
||||
previousEmployees: null,
|
||||
position: 9,
|
||||
linkedinLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'CompanyEdge',
|
||||
cursor: 'WzEwLCAiMjAyMDIwMjAtZjg2Yi00MTlmLWI3OTQtMDIzMTlhYmU4NjM3Il0=',
|
||||
node: {
|
||||
__typename: 'Company',
|
||||
id: '20202020-f86b-419f-b794-02319abe8637',
|
||||
employees: null,
|
||||
createdAt: '2024-06-05T09:00:20.412Z',
|
||||
name: 'Hasura',
|
||||
idealCustomerProfile: false,
|
||||
accountOwner: null,
|
||||
domainName: 'hasura.io',
|
||||
address: '',
|
||||
previousEmployees: null,
|
||||
position: 10,
|
||||
linkedinLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'CompanyEdge',
|
||||
cursor: 'WzExLCAiMjAyMDIwMjAtNTUxOC00NTUzLTk0MzMtNDJkOGViODI4MzRiIl0=',
|
||||
node: {
|
||||
__typename: 'Company',
|
||||
id: '20202020-5518-4553-9433-42d8eb82834b',
|
||||
employees: null,
|
||||
createdAt: '2024-06-05T09:00:20.412Z',
|
||||
name: 'Wework',
|
||||
idealCustomerProfile: false,
|
||||
accountOwner: null,
|
||||
domainName: 'wework.com',
|
||||
address: '',
|
||||
previousEmployees: null,
|
||||
position: 11,
|
||||
linkedinLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'CompanyEdge',
|
||||
cursor: 'WzEyLCAiMjAyMDIwMjAtZjc5ZS00MGRkLWJkMDYtYzM2ZTZhYmI0Njc4Il0=',
|
||||
node: {
|
||||
__typename: 'Company',
|
||||
id: '20202020-f79e-40dd-bd06-c36e6abb4678',
|
||||
employees: null,
|
||||
createdAt: '2024-06-05T09:00:20.412Z',
|
||||
name: 'Samsung',
|
||||
idealCustomerProfile: false,
|
||||
accountOwner: null,
|
||||
domainName: 'samsung.com',
|
||||
address: '',
|
||||
previousEmployees: null,
|
||||
position: 12,
|
||||
linkedinLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'CompanyEdge',
|
||||
cursor: 'WzEzLCAiMjAyMDIwMjAtMTQ1NS00YzU3LWFmYWYtZGQ1ZGMwODYzNjFkIl0=',
|
||||
node: {
|
||||
__typename: 'Company',
|
||||
id: '20202020-1455-4c57-afaf-dd5dc086361d',
|
||||
employees: null,
|
||||
createdAt: '2024-06-05T09:00:20.412Z',
|
||||
name: 'Algolia',
|
||||
idealCustomerProfile: false,
|
||||
accountOwner: null,
|
||||
domainName: 'algolia.com',
|
||||
address: '',
|
||||
previousEmployees: null,
|
||||
position: 13,
|
||||
linkedinLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
__typename: 'Company',
|
||||
id: 'b396e6b9-dc5c-4643-bcff-61b6cf7523ae',
|
||||
domainName: 'aircall.io',
|
||||
name: 'Aircall',
|
||||
createdAt: '2023-04-26T10:12:42.33625+00:00',
|
||||
updatedAt: '2023-04-26T10:23:42.33625+00:00',
|
||||
address: '',
|
||||
employees: 1,
|
||||
accountOwnerId: null,
|
||||
linkedinLink: {
|
||||
url: 'https://www.linkedin.com/company/aircall/',
|
||||
label: 'https://www.linkedin.com/company/aircall/',
|
||||
},
|
||||
xLink: {
|
||||
url: 'https://twitter.com/aircall',
|
||||
label: 'https://twitter.com/aircall',
|
||||
},
|
||||
annualRecurringRevenue: { amountMicros: 500000, currencyCode: 'USD' },
|
||||
idealCustomerProfile: false,
|
||||
accountOwner: null,
|
||||
Favorite: null,
|
||||
},
|
||||
{
|
||||
__typename: 'Company',
|
||||
id: 'a674fa6c-1455-4c57-afaf-dd5dc086361d',
|
||||
domainName: 'algolia.com',
|
||||
name: 'Algolia',
|
||||
createdAt: '2023-04-26T10:10:32.530184+00:00',
|
||||
updatedAt: '2023-04-26T10:23:42.33625+00:00',
|
||||
address: '',
|
||||
employees: 1,
|
||||
linkedinLink: {
|
||||
url: 'https://www.linkedin.com/company/algolia/',
|
||||
label: 'https://www.linkedin.com/company/algolia/',
|
||||
},
|
||||
xLink: {
|
||||
url: 'https://twitter.com/algolia',
|
||||
label: 'https://twitter.com/algolia',
|
||||
},
|
||||
annualRecurringRevenue: { amountMicros: 5000000, currencyCode: 'USD' },
|
||||
idealCustomerProfile: true,
|
||||
accountOwner: null,
|
||||
Favorite: null,
|
||||
accountOwnerId: null,
|
||||
},
|
||||
{
|
||||
__typename: 'Company',
|
||||
id: 'b1cfd51b-a831-455f-ba07-4e30671e1dc3',
|
||||
domainName: 'apple.com',
|
||||
name: 'Apple',
|
||||
createdAt: '2023-03-21T06:30:25.39474+00:00',
|
||||
updatedAt: '2023-04-26T10:23:42.33625+00:00',
|
||||
address: '',
|
||||
employees: 10,
|
||||
linkedinLink: {
|
||||
url: 'https://www.linkedin.com/company/apple/',
|
||||
label: 'https://www.linkedin.com/company/apple/',
|
||||
},
|
||||
xLink: {
|
||||
url: 'https://twitter.com/apple',
|
||||
label: 'https://twitter.com/apple',
|
||||
},
|
||||
annualRecurringRevenue: { amountMicros: 1000000, currencyCode: 'USD' },
|
||||
idealCustomerProfile: false,
|
||||
accountOwner: null,
|
||||
Favorite: null,
|
||||
accountOwnerId: null,
|
||||
},
|
||||
{
|
||||
__typename: 'Company',
|
||||
id: '5c21e19e-e049-4393-8c09-3e3f8fb09ecb',
|
||||
domainName: 'qonto.com',
|
||||
name: 'Qonto',
|
||||
createdAt: '2023-04-26T10:13:29.712485+00:00',
|
||||
updatedAt: '2023-04-26T10:23:42.33625+00:00',
|
||||
address: '10 rue de la Paix',
|
||||
employees: 1,
|
||||
linkedinLink: {
|
||||
url: 'https://www.linkedin.com/company/qonto/',
|
||||
label: 'https://www.linkedin.com/company/qonto/',
|
||||
},
|
||||
xLink: {
|
||||
url: 'https://twitter.com/qonto',
|
||||
label: 'https://twitter.com/qonto',
|
||||
},
|
||||
annualRecurringRevenue: { amountMicros: 5000000, currencyCode: 'USD' },
|
||||
idealCustomerProfile: false,
|
||||
accountOwner: null,
|
||||
Favorite: null,
|
||||
accountOwnerId: null,
|
||||
},
|
||||
{
|
||||
__typename: 'Company',
|
||||
id: '9d162de6-cfbf-4156-a790-e39854dcd4eb',
|
||||
domainName: 'facebook.com',
|
||||
name: 'Facebook',
|
||||
createdAt: '2023-04-26T10:09:25.656555+00:00',
|
||||
updatedAt: '2023-04-26T10:23:42.33625+00:00',
|
||||
address: '',
|
||||
employees: 1,
|
||||
linkedinLink: {
|
||||
url: 'https://www.linkedin.com/company/facebook/',
|
||||
label: 'https://www.linkedin.com/company/facebook/',
|
||||
},
|
||||
xLink: {
|
||||
url: 'https://twitter.com/facebook',
|
||||
label: 'https://twitter.com/facebook',
|
||||
},
|
||||
annualRecurringRevenue: { amountMicros: 5000000, currencyCode: 'USD' },
|
||||
idealCustomerProfile: true,
|
||||
accountOwner: null,
|
||||
Favorite: null,
|
||||
accountOwnerId: null,
|
||||
},
|
||||
{
|
||||
__typename: 'Company',
|
||||
id: '9d162de6-cfbf-4156-a790-e39854dcd4ef',
|
||||
domainName: 'sequoia.com',
|
||||
name: 'Sequoia',
|
||||
createdAt: '2023-04-26T10:09:25.656555+00:00',
|
||||
updatedAt: '2023-04-26T10:23:42.33625+00:00',
|
||||
address: '',
|
||||
employees: 1,
|
||||
linkedinLink: {
|
||||
url: 'https://www.linkedin.com/company/sequoia/',
|
||||
label: 'https://www.linkedin.com/company/sequoia/',
|
||||
},
|
||||
xLink: {
|
||||
url: 'https://twitter.com/sequoia',
|
||||
label: 'https://twitter.com/sequoia',
|
||||
},
|
||||
annualRecurringRevenue: { amountMicros: 5000000, currencyCode: 'USD' },
|
||||
idealCustomerProfile: true,
|
||||
accountOwner: null,
|
||||
Favorite: null,
|
||||
accountOwnerId: null,
|
||||
},
|
||||
];
|
||||
|
||||
export const mockedDuplicateCompanyData: MockedCompany = {
|
||||
...mockedCompaniesData[0],
|
||||
id: '8b40856a-2ec9-4c03-8bc0-c032c89e1824',
|
||||
};
|
||||
|
||||
export const mockedEmptyCompanyData = {
|
||||
id: '9231e6ee-4cc2-4c7b-8c55-dff16f4d968a',
|
||||
name: '',
|
||||
domainName: '',
|
||||
address: '',
|
||||
accountOwner: null,
|
||||
annualRecurringRevenue: null,
|
||||
createdAt: null,
|
||||
updatedAt: null,
|
||||
employees: null,
|
||||
idealCustomerProfile: null,
|
||||
linkedinLink: null,
|
||||
xLink: null,
|
||||
_activityCount: null,
|
||||
__typename: 'Company',
|
||||
};
|
||||
|
@ -1,493 +0,0 @@
|
||||
import { Company } from '@/companies/types/Company';
|
||||
import { Favorite } from '@/favorites/types/Favorite';
|
||||
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
|
||||
import { mockedCompaniesData } from '~/testing/mock-data/companies';
|
||||
|
||||
type MockedCompanyV2 = Omit<Company, 'deletedAt'> & {
|
||||
accountOwner: WorkspaceMember | null;
|
||||
Favorite?: Pick<Favorite, 'id'> | null;
|
||||
};
|
||||
|
||||
export const mockedCompaniesDataV2: Array<MockedCompanyV2> = [
|
||||
{
|
||||
__typename: 'Company',
|
||||
domainName: 'paris.com',
|
||||
name: 'Test',
|
||||
employees: null,
|
||||
address: 'Paris France',
|
||||
createdAt: '2024-05-27T11:23:05.954Z',
|
||||
id: 'd55c240e-e4e0-4248-b56d-8004d1218a9c',
|
||||
position: 6.109375,
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: 1000000000,
|
||||
currencyCode: 'USD',
|
||||
},
|
||||
linkedinLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: 'paris.com',
|
||||
},
|
||||
accountOwner: {
|
||||
__typename: 'WorkspaceMember',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-1553-45c6-a028-5a9064cce07f',
|
||||
colorScheme: 'Light',
|
||||
updatedAt: '2024-05-01T13:16:29.046Z',
|
||||
locale: 'en',
|
||||
avatarUrl: '',
|
||||
userId: '20202020-7169-42cf-bc47-1cfef15264b8',
|
||||
userEmail: 'phil.schiler@apple.dev',
|
||||
name: {
|
||||
__typename: 'FullName',
|
||||
firstName: 'Phil',
|
||||
lastName: 'Shiler',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'Company',
|
||||
domainName: 'google.com',
|
||||
name: 'Google',
|
||||
employees: 10202,
|
||||
address: 'Paris France',
|
||||
createdAt: '2024-05-21T13:16:29.000Z',
|
||||
id: '20202020-c21e-4ec2-873b-de4264d89025',
|
||||
position: 7.5,
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: 1001000000,
|
||||
currencyCode: 'USD',
|
||||
},
|
||||
linkedinLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: '',
|
||||
},
|
||||
accountOwner: {
|
||||
__typename: 'WorkspaceMember',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-0687-4c41-b707-ed1bfca972a7',
|
||||
colorScheme: 'Light',
|
||||
updatedAt: '2024-05-30T09:00:31.127Z',
|
||||
locale: 'en',
|
||||
avatarUrl: '',
|
||||
userId: '20202020-9e3b-46d4-a556-88b9ddc2b034',
|
||||
userEmail: 'tim@apple.dev',
|
||||
name: {
|
||||
__typename: 'FullName',
|
||||
firstName: 'Tim',
|
||||
lastName: 'Apple',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'Company',
|
||||
domainName: 'hasura.io',
|
||||
name: 'Hasura',
|
||||
employees: 102938102938,
|
||||
address: '',
|
||||
createdAt: '2024-05-16T13:16:29.000Z',
|
||||
id: '20202020-f86b-419f-b794-02319abe8637',
|
||||
position: 10,
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
linkedinLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: '',
|
||||
},
|
||||
accountOwner: {
|
||||
__typename: 'WorkspaceMember',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-77d5-4cb6-b60a-f4a835a85d61',
|
||||
colorScheme: 'Light',
|
||||
updatedAt: '2024-05-01T13:16:29.046Z',
|
||||
locale: 'en',
|
||||
avatarUrl: '',
|
||||
userId: '20202020-3957-4908-9c36-2929a23f8357',
|
||||
userEmail: 'jony.ive@apple.dev',
|
||||
name: {
|
||||
__typename: 'FullName',
|
||||
firstName: 'Jony',
|
||||
lastName: 'Ive',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'Company',
|
||||
domainName: 'netflix.com',
|
||||
name: 'Netflix',
|
||||
employees: null,
|
||||
address: '',
|
||||
createdAt: '2024-05-15T13:16:29.000Z',
|
||||
id: '20202020-707e-44dc-a1d2-30030bf1a944',
|
||||
position: 7,
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: 2000000000,
|
||||
currencyCode: 'USD',
|
||||
},
|
||||
linkedinLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: '',
|
||||
},
|
||||
accountOwner: {
|
||||
__typename: 'WorkspaceMember',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-1553-45c6-a028-5a9064cce07f',
|
||||
colorScheme: 'Light',
|
||||
updatedAt: '2024-05-01T13:16:29.046Z',
|
||||
locale: 'en',
|
||||
avatarUrl: '',
|
||||
userId: '20202020-7169-42cf-bc47-1cfef15264b8',
|
||||
userEmail: 'phil.schiler@apple.dev',
|
||||
name: {
|
||||
__typename: 'FullName',
|
||||
firstName: 'Phil',
|
||||
lastName: 'Shiler',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'Company',
|
||||
domainName: 'claap.io',
|
||||
name: 'Claap',
|
||||
employees: 2131920,
|
||||
address: 'asdasd',
|
||||
createdAt: '2024-05-10T13:16:29.000Z',
|
||||
id: '20202020-cfbf-4156-a790-e39854dcd4eb',
|
||||
position: 9,
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
linkedinLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: 'asmdlkasd',
|
||||
},
|
||||
accountOwner: {
|
||||
__typename: 'WorkspaceMember',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-0687-4c41-b707-ed1bfca972a7',
|
||||
colorScheme: 'Light',
|
||||
updatedAt: '2024-05-30T09:00:31.127Z',
|
||||
locale: 'en',
|
||||
avatarUrl: '',
|
||||
userId: '20202020-9e3b-46d4-a556-88b9ddc2b034',
|
||||
userEmail: 'tim@apple.dev',
|
||||
name: {
|
||||
__typename: 'FullName',
|
||||
firstName: 'Tim',
|
||||
lastName: 'Apple',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'Company',
|
||||
domainName: 'libeo.io',
|
||||
name: 'Libeo',
|
||||
employees: 1239819238,
|
||||
address: '',
|
||||
createdAt: '2024-05-09T13:16:29.000Z',
|
||||
id: '20202020-3f74-492d-a101-2a70f50a1645',
|
||||
position: 8,
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
linkedinLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: '',
|
||||
},
|
||||
accountOwner: {
|
||||
__typename: 'WorkspaceMember',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-1553-45c6-a028-5a9064cce07f',
|
||||
colorScheme: 'Light',
|
||||
updatedAt: '2024-05-01T13:16:29.046Z',
|
||||
locale: 'en',
|
||||
avatarUrl: '',
|
||||
userId: '20202020-7169-42cf-bc47-1cfef15264b8',
|
||||
userEmail: 'phil.schiler@apple.dev',
|
||||
name: {
|
||||
__typename: 'FullName',
|
||||
firstName: 'Phil',
|
||||
lastName: 'Shiler',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'Company',
|
||||
domainName: 'qonto.com',
|
||||
name: 'Qonto',
|
||||
employees: 123123123,
|
||||
address: '',
|
||||
createdAt: '2024-05-08T13:16:29.000Z',
|
||||
id: '20202020-0713-40a5-8216-82802401d33e',
|
||||
position: 9.5,
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
linkedinLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: '',
|
||||
},
|
||||
accountOwner: {
|
||||
__typename: 'WorkspaceMember',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-1553-45c6-a028-5a9064cce07f',
|
||||
colorScheme: 'Light',
|
||||
updatedAt: '2024-05-01T13:16:29.046Z',
|
||||
locale: 'en',
|
||||
avatarUrl: '',
|
||||
userId: '20202020-7169-42cf-bc47-1cfef15264b8',
|
||||
userEmail: 'phil.schiler@apple.dev',
|
||||
name: {
|
||||
__typename: 'FullName',
|
||||
firstName: 'Phil',
|
||||
lastName: 'Shiler',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'Company',
|
||||
domainName: 'wework.com',
|
||||
name: 'Wework',
|
||||
employees: 123123123,
|
||||
address: '',
|
||||
createdAt: '2024-05-08T13:16:29.000Z',
|
||||
id: '20202020-5518-4553-9433-42d8eb82834b',
|
||||
position: 11,
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
linkedinLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: '',
|
||||
},
|
||||
accountOwner: {
|
||||
__typename: 'WorkspaceMember',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-0687-4c41-b707-ed1bfca972a7',
|
||||
colorScheme: 'Light',
|
||||
updatedAt: '2024-05-30T09:00:31.127Z',
|
||||
locale: 'en',
|
||||
avatarUrl: '',
|
||||
userId: '20202020-9e3b-46d4-a556-88b9ddc2b034',
|
||||
userEmail: 'tim@apple.dev',
|
||||
name: {
|
||||
__typename: 'FullName',
|
||||
firstName: 'Tim',
|
||||
lastName: 'Apple',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'Company',
|
||||
domainName: 'linkedin.com',
|
||||
name: 'Linkedin',
|
||||
employees: 10102,
|
||||
accountOwner: null,
|
||||
address: '',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-3ec3-4fe3-8997-b76aa0bfa408',
|
||||
position: 1,
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
linkedinLink: {
|
||||
__typename: 'Link',
|
||||
label: 'adasd',
|
||||
url: 'adasd',
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'Company',
|
||||
domainName: 'airbnb.com',
|
||||
name: 'Airbnb',
|
||||
employees: 123333,
|
||||
accountOwner: null,
|
||||
address: '',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-171e-4bcc-9cf7-43448d6fb278',
|
||||
position: 5,
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
linkedinLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'Company',
|
||||
domainName: 'samsung.com',
|
||||
name: 'Samsung',
|
||||
employees: 10000,
|
||||
address: '',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-f79e-40dd-bd06-c36e6abb4678',
|
||||
position: 12,
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
linkedinLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: '',
|
||||
},
|
||||
accountOwner: {
|
||||
__typename: 'WorkspaceMember',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-1553-45c6-a028-5a9064cce07f',
|
||||
colorScheme: 'Light',
|
||||
updatedAt: '2024-05-01T13:16:29.046Z',
|
||||
locale: 'en',
|
||||
avatarUrl: '',
|
||||
userId: '20202020-7169-42cf-bc47-1cfef15264b8',
|
||||
userEmail: 'phil.schiler@apple.dev',
|
||||
name: {
|
||||
__typename: 'FullName',
|
||||
firstName: 'Phil',
|
||||
lastName: 'Shiler',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'Company',
|
||||
domainName: 'algolia.com',
|
||||
name: 'Algolia',
|
||||
employees: 10000,
|
||||
address: '',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-1455-4c57-afaf-dd5dc086361d',
|
||||
position: 13,
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
linkedinLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: '',
|
||||
},
|
||||
accountOwner: {
|
||||
__typename: 'WorkspaceMember',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-77d5-4cb6-b60a-f4a835a85d61',
|
||||
colorScheme: 'Light',
|
||||
updatedAt: '2024-05-01T13:16:29.046Z',
|
||||
locale: 'en',
|
||||
avatarUrl: '',
|
||||
userId: '20202020-3957-4908-9c36-2929a23f8357',
|
||||
userEmail: 'jony.ive@apple.dev',
|
||||
name: {
|
||||
__typename: 'FullName',
|
||||
firstName: 'Jony',
|
||||
lastName: 'Ive',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'Company',
|
||||
domainName: 'facebook.com',
|
||||
name: 'Facebook',
|
||||
employees: 220323,
|
||||
accountOwner: null,
|
||||
address: '',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-5d81-46d6-bf83-f7fd33ea6102',
|
||||
position: 6.0625,
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
linkedinLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: 'asdasd',
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'Company',
|
||||
domainName: 'microsoft.com',
|
||||
name: 'Microsoft',
|
||||
employees: 10000,
|
||||
address: '',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-ed89-413a-b31a-962986e67bb4',
|
||||
position: 6.09375,
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: 10000000000,
|
||||
currencyCode: 'USD',
|
||||
},
|
||||
linkedinLink: {
|
||||
__typename: 'Link',
|
||||
label: '',
|
||||
url: '',
|
||||
},
|
||||
accountOwner: {
|
||||
__typename: 'WorkspaceMember',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-0687-4c41-b707-ed1bfca972a7',
|
||||
colorScheme: 'Light',
|
||||
updatedAt: '2024-05-30T09:00:31.127Z',
|
||||
locale: 'en',
|
||||
avatarUrl: '',
|
||||
userId: '20202020-9e3b-46d4-a556-88b9ddc2b034',
|
||||
userEmail: 'tim@apple.dev',
|
||||
name: {
|
||||
__typename: 'FullName',
|
||||
firstName: 'Tim',
|
||||
lastName: 'Apple',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const mockedDuplicateCompanyData: MockedCompanyV2 = {
|
||||
...mockedCompaniesData[0],
|
||||
id: '8b40856a-2ec9-4c03-8bc0-c032c89e1824',
|
||||
};
|
||||
|
||||
export const mockedEmptyCompanyData = {
|
||||
id: '9231e6ee-4cc2-4c7b-8c55-dff16f4d968a',
|
||||
name: '',
|
||||
domainName: '',
|
||||
address: '',
|
||||
accountOwner: null,
|
||||
annualRecurringRevenue: null,
|
||||
createdAt: null,
|
||||
updatedAt: null,
|
||||
employees: null,
|
||||
idealCustomerProfile: null,
|
||||
linkedinLink: null,
|
||||
xLink: null,
|
||||
_activityCount: null,
|
||||
__typename: 'Company',
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,4 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationMetadataType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { mockedStandardObjectMetadataQueryResult } from '~/testing/mock-data/generated/standard-metadata-query-result';
|
||||
|
||||
export const generatedMockObjectMetadataItems: ObjectMetadataItem[] =
|
||||
@ -10,431 +6,3 @@ export const generatedMockObjectMetadataItems: ObjectMetadataItem[] =
|
||||
...edge.node,
|
||||
fields: edge.node.fields.edges.map((edge) => edge.node),
|
||||
}));
|
||||
|
||||
export const mockObjectMetadataItem: ObjectMetadataItem = {
|
||||
__typename: 'object',
|
||||
id: 'b79a038c-b06b-4a5a-b7ee-f8ba412aa1c0',
|
||||
nameSingular: 'company',
|
||||
namePlural: 'companies',
|
||||
labelSingular: 'Company',
|
||||
labelPlural: 'Companies',
|
||||
description: 'A company',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isCustom: false,
|
||||
isRemote: false,
|
||||
isActive: true,
|
||||
isSystem: false,
|
||||
createdAt: '2023-12-19T12:15:28.459Z',
|
||||
updatedAt: '2023-12-19T12:15:28.459Z',
|
||||
labelIdentifierFieldMetadataId: null,
|
||||
imageIdentifierFieldMetadataId: null,
|
||||
fields: [
|
||||
{
|
||||
__typename: 'field',
|
||||
id: '390eb5e5-d8d1-4064-bf75-3461251eb142',
|
||||
type: FieldMetadataType.Boolean,
|
||||
name: 'idealCustomerProfile',
|
||||
label: 'ICP',
|
||||
description:
|
||||
'Ideal Customer Profile: Indicates whether the company is the most suitable and valuable customer for you',
|
||||
icon: 'IconTarget',
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
isSystem: false,
|
||||
isNullable: true,
|
||||
createdAt: '2023-12-19T12:15:28.459Z',
|
||||
updatedAt: '2023-12-19T12:15:28.459Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
},
|
||||
{
|
||||
__typename: 'field',
|
||||
id: '72a43010-f236-4fa2-8ac4-a31e6b37d692',
|
||||
type: FieldMetadataType.Relation,
|
||||
name: 'people',
|
||||
label: 'People',
|
||||
description: 'People linked to the company.',
|
||||
icon: 'IconUsers',
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
isSystem: false,
|
||||
isNullable: true,
|
||||
createdAt: '2023-12-19T12:15:28.459Z',
|
||||
updatedAt: '2023-12-19T12:15:28.459Z',
|
||||
fromRelationMetadata: {
|
||||
id: 'f08943fe-e8a0-4747-951c-c3b391842453',
|
||||
relationType: RelationMetadataType.OneToMany,
|
||||
toObjectMetadata: {
|
||||
id: 'fcccc985-5edf-405c-aa2b-80c82b230f35',
|
||||
nameSingular: 'person',
|
||||
namePlural: 'people',
|
||||
isSystem: false,
|
||||
isRemote: false,
|
||||
},
|
||||
toFieldMetadataId: 'c756f6ff-8c00-4fe5-a923-c6cfc7b1ac4a',
|
||||
},
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
},
|
||||
{
|
||||
__typename: 'field',
|
||||
id: '51636fba-1bd9-4344-bba8-9639cbc8e134',
|
||||
type: FieldMetadataType.Relation,
|
||||
name: 'opportunities',
|
||||
label: 'Opportunities',
|
||||
description: 'Opportunities linked to the company.',
|
||||
icon: 'IconTargetArrow',
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
isSystem: false,
|
||||
isNullable: true,
|
||||
createdAt: '2023-12-19T12:15:28.459Z',
|
||||
updatedAt: '2023-12-19T12:15:28.459Z',
|
||||
fromRelationMetadata: {
|
||||
id: '7ffae8bb-b12b-4ad9-8922-da0d517b5612',
|
||||
relationType: RelationMetadataType.OneToMany,
|
||||
toObjectMetadata: {
|
||||
id: '169e5b21-dc95-44a8-acd0-5e9447dd0784',
|
||||
nameSingular: 'opportunity',
|
||||
namePlural: 'opportunities',
|
||||
isSystem: false,
|
||||
isRemote: false,
|
||||
},
|
||||
toFieldMetadataId: '00468e2a-a601-4635-ae9c-a9bb826cc860',
|
||||
},
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
},
|
||||
{
|
||||
__typename: 'field',
|
||||
id: 'd541f76b-d327-4dda-8ef8-81b60e5ad01e',
|
||||
type: FieldMetadataType.Relation,
|
||||
name: 'activityTargets',
|
||||
label: 'Activities',
|
||||
description: 'Activities tied to the company',
|
||||
icon: 'IconCheckbox',
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
isSystem: false,
|
||||
isNullable: true,
|
||||
createdAt: '2023-12-19T12:15:28.459Z',
|
||||
updatedAt: '2023-12-19T12:15:28.459Z',
|
||||
fromRelationMetadata: {
|
||||
id: 'bc42672b-350f-45c3-bd1f-4debb536ccd1',
|
||||
relationType: RelationMetadataType.OneToMany,
|
||||
toObjectMetadata: {
|
||||
id: 'b87c6cac-a8e7-4156-a525-30ec536acd75',
|
||||
nameSingular: 'activityTarget',
|
||||
namePlural: 'activityTargets',
|
||||
isSystem: true,
|
||||
isRemote: false,
|
||||
},
|
||||
toFieldMetadataId: 'bba19feb-c248-487b-92d7-98df54c51e44',
|
||||
},
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
},
|
||||
{
|
||||
__typename: 'field',
|
||||
id: 'dacb7562-497e-4080-8ef5-746d6786ed49',
|
||||
type: FieldMetadataType.DateTime,
|
||||
name: 'createdAt',
|
||||
label: 'Creation date',
|
||||
description: null,
|
||||
icon: 'IconCalendar',
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
isSystem: false,
|
||||
isNullable: false,
|
||||
createdAt: '2023-12-19T12:15:28.459Z',
|
||||
updatedAt: '2023-12-19T12:15:28.459Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: {
|
||||
type: 'now',
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'field',
|
||||
id: 'f3b4ff22-800b-4f13-8262-8003da8eed5b',
|
||||
type: FieldMetadataType.Number,
|
||||
name: 'employees',
|
||||
label: 'Employees',
|
||||
description: 'Number of employees in the company',
|
||||
icon: 'IconUsers',
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
isSystem: false,
|
||||
isNullable: true,
|
||||
createdAt: '2023-12-19T12:15:28.459Z',
|
||||
updatedAt: '2023-12-19T12:15:28.459Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
},
|
||||
{
|
||||
__typename: 'field',
|
||||
id: 'c3e64012-32cc-43f1-af2f-33b37cc4e59d',
|
||||
type: FieldMetadataType.Link,
|
||||
name: 'linkedinLink',
|
||||
label: 'Linkedin',
|
||||
description: 'The company Linkedin account',
|
||||
icon: 'IconBrandLinkedin',
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
isSystem: false,
|
||||
isNullable: true,
|
||||
createdAt: '2023-12-19T12:15:28.459Z',
|
||||
updatedAt: '2023-12-19T12:15:28.459Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
},
|
||||
{
|
||||
__typename: 'field',
|
||||
id: 'fced9acc-0374-487d-9da4-579a17435df0',
|
||||
type: FieldMetadataType.Link,
|
||||
name: 'xLink',
|
||||
label: 'X',
|
||||
description: 'The company Twitter/X account',
|
||||
icon: 'IconBrandX',
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
isSystem: false,
|
||||
isNullable: true,
|
||||
createdAt: '2023-12-19T12:15:28.459Z',
|
||||
updatedAt: '2023-12-19T12:15:28.459Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
},
|
||||
{
|
||||
__typename: 'field',
|
||||
id: '63db0a2f-ffb4-4ea1-98c7-f7e13ce75c38',
|
||||
type: FieldMetadataType.Relation,
|
||||
name: 'attachments',
|
||||
label: 'Attachments',
|
||||
description: 'Attachments linked to the company.',
|
||||
icon: 'IconFileImport',
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
isSystem: false,
|
||||
isNullable: true,
|
||||
createdAt: '2023-12-19T12:15:28.459Z',
|
||||
updatedAt: '2023-12-19T12:15:28.459Z',
|
||||
fromRelationMetadata: {
|
||||
id: '901fd405-c6bf-4559-9d1f-d0937b6f16d9',
|
||||
relationType: RelationMetadataType.OneToMany,
|
||||
toObjectMetadata: {
|
||||
id: '77240b4b-6bcf-454d-a102-19bbba181716',
|
||||
nameSingular: 'attachment',
|
||||
namePlural: 'attachments',
|
||||
isSystem: true,
|
||||
isRemote: false,
|
||||
},
|
||||
toFieldMetadataId: '0880dac5-37d2-43a6-b143-722126d4923f',
|
||||
},
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
},
|
||||
{
|
||||
__typename: 'field',
|
||||
id: 'e775ce12-87c0-4feb-bcfe-9af3d8ca117b',
|
||||
type: FieldMetadataType.Uuid,
|
||||
name: 'id',
|
||||
label: 'Id',
|
||||
description: null,
|
||||
icon: null,
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
isSystem: true,
|
||||
isNullable: false,
|
||||
createdAt: '2023-12-19T12:15:28.459Z',
|
||||
updatedAt: '2023-12-19T12:15:28.459Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: {
|
||||
type: 'uuid',
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'field',
|
||||
id: '2278ef91-3d6a-45cf-86f5-76b7bfa2bf32',
|
||||
type: FieldMetadataType.Text,
|
||||
name: 'domainName',
|
||||
label: 'Domain Name',
|
||||
description:
|
||||
'The company website URL. We use this url to fetch the company icon',
|
||||
icon: 'IconLink',
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
isSystem: false,
|
||||
isNullable: true,
|
||||
createdAt: '2023-12-19T12:15:28.459Z',
|
||||
updatedAt: '2023-12-19T12:15:28.459Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: {
|
||||
value: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'field',
|
||||
id: '438291d7-18f4-48cf-8dca-05e96c5a0765',
|
||||
type: FieldMetadataType.Currency,
|
||||
name: 'annualRecurringRevenue',
|
||||
label: 'ARR',
|
||||
description:
|
||||
'Annual Recurring Revenue: The actual or estimated annual revenue of the company',
|
||||
icon: 'IconMoneybag',
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
isSystem: false,
|
||||
isNullable: true,
|
||||
createdAt: '2023-12-19T12:15:28.459Z',
|
||||
updatedAt: '2023-12-19T12:15:28.459Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
},
|
||||
{
|
||||
__typename: 'field',
|
||||
id: 'edb8475f-03fc-4ac1-9305-e9d4e2dacd11',
|
||||
type: FieldMetadataType.DateTime,
|
||||
name: 'updatedAt',
|
||||
label: 'Update date',
|
||||
description: null,
|
||||
icon: 'IconCalendar',
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
isSystem: true,
|
||||
isNullable: false,
|
||||
createdAt: '2023-12-19T12:15:28.459Z',
|
||||
updatedAt: '2023-12-19T12:15:28.459Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: {
|
||||
type: 'now',
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'field',
|
||||
id: 'e3c9ba7f-cecf-4ac6-a7b9-7a9987be0253',
|
||||
type: FieldMetadataType.Relation,
|
||||
name: 'accountOwner',
|
||||
label: 'Account Owner',
|
||||
description:
|
||||
'Your team member responsible for managing the company account',
|
||||
icon: 'IconUserCircle',
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
isSystem: false,
|
||||
isNullable: true,
|
||||
createdAt: '2023-12-19T12:15:28.459Z',
|
||||
updatedAt: '2023-12-19T12:15:28.459Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: {
|
||||
id: '0317d74c-5187-491f-9e1d-d22f06ca2a38',
|
||||
relationType: RelationMetadataType.OneToMany,
|
||||
fromObjectMetadata: {
|
||||
id: '92c306ce-ad06-4712-99d2-5d0daf13c95f',
|
||||
nameSingular: 'workspaceMember',
|
||||
namePlural: 'workspaceMembers',
|
||||
isSystem: true,
|
||||
isRemote: false,
|
||||
},
|
||||
fromFieldMetadataId: '0f3e456f-3bb4-4261-a436-95246dc0e159',
|
||||
},
|
||||
defaultValue: null,
|
||||
},
|
||||
{
|
||||
__typename: 'field',
|
||||
id: 'a34bd3b3-6949-4793-bac6-d2c054639c7f',
|
||||
type: FieldMetadataType.Text,
|
||||
name: 'address',
|
||||
label: 'Address',
|
||||
description: 'The company address',
|
||||
icon: 'IconMap',
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
isSystem: false,
|
||||
isNullable: true,
|
||||
createdAt: '2023-12-19T12:15:28.459Z',
|
||||
updatedAt: '2023-12-19T12:15:28.459Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: {
|
||||
value: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'field',
|
||||
id: '4b204845-f1fc-4fd8-8fdd-f4caeaab749f',
|
||||
type: FieldMetadataType.Relation,
|
||||
name: 'favorites',
|
||||
label: 'Favorites',
|
||||
description: 'Favorites linked to the company',
|
||||
icon: 'IconHeart',
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
isSystem: true,
|
||||
isNullable: true,
|
||||
createdAt: '2023-12-19T12:15:28.459Z',
|
||||
updatedAt: '2023-12-19T12:15:28.459Z',
|
||||
fromRelationMetadata: {
|
||||
id: '8e0d3aa1-6135-4d65-aa28-15a5b6d1619c',
|
||||
relationType: RelationMetadataType.OneToMany,
|
||||
toObjectMetadata: {
|
||||
id: '1415392e-0ecb-462e-aa67-001e424e6a37',
|
||||
nameSingular: 'favorite',
|
||||
namePlural: 'favorites',
|
||||
isSystem: true,
|
||||
isRemote: false,
|
||||
},
|
||||
toFieldMetadataId: '8fd8965b-bd4e-4a9b-90e9-c75652dadda1',
|
||||
},
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
},
|
||||
{
|
||||
__typename: 'field',
|
||||
id: 'a795e81e-0bcf-4fd6-8f2f-b3764b990d2d',
|
||||
type: FieldMetadataType.Uuid,
|
||||
name: 'accountOwnerId',
|
||||
label: 'Account Owner id (foreign key)',
|
||||
description:
|
||||
'Your team member responsible for managing the company account id foreign key',
|
||||
icon: 'IconUserCircle',
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
isSystem: true,
|
||||
isNullable: true,
|
||||
createdAt: '2023-12-19T12:15:28.459Z',
|
||||
updatedAt: '2023-12-19T12:15:28.459Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: null,
|
||||
},
|
||||
{
|
||||
__typename: 'field',
|
||||
id: '87887d23-f632-4d3e-840a-02fcee960660',
|
||||
type: FieldMetadataType.Text,
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
description: 'The company name',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
isCustom: false,
|
||||
isActive: true,
|
||||
isSystem: false,
|
||||
isNullable: false,
|
||||
createdAt: '2023-12-19T12:15:28.459Z',
|
||||
updatedAt: '2023-12-19T12:15:28.459Z',
|
||||
fromRelationMetadata: null,
|
||||
toRelationMetadata: null,
|
||||
defaultValue: {
|
||||
value: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,533 +0,0 @@
|
||||
import { Company } from '@/companies/types/Company';
|
||||
import { Person } from '@/people/types/Person';
|
||||
|
||||
export type MockedPersonV2 = Pick<
|
||||
Person,
|
||||
| '__typename'
|
||||
| 'id'
|
||||
| 'name'
|
||||
| 'linkedinLink'
|
||||
| 'xLink'
|
||||
| 'links'
|
||||
| 'jobTitle'
|
||||
| 'email'
|
||||
| 'phone'
|
||||
| 'city'
|
||||
| 'avatarUrl'
|
||||
| 'createdAt'
|
||||
| 'updatedAt'
|
||||
| 'companyId'
|
||||
| 'position'
|
||||
> & {
|
||||
company?: Company;
|
||||
};
|
||||
|
||||
export const mockPeopleDataV2: MockedPersonV2[] = [
|
||||
{
|
||||
__typename: 'Person',
|
||||
city: 'Seattle',
|
||||
jobTitle: '',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-1c0e-494c-a1b6-85b1c6fefaa5',
|
||||
email: 'christoph.calisto@linkedin.com',
|
||||
phone: '+33789012345',
|
||||
position: 1,
|
||||
name: {
|
||||
__typename: 'FullName',
|
||||
firstName: 'Christoph',
|
||||
lastName: 'Callisto',
|
||||
},
|
||||
linkedinLink: { __typename: 'Link', label: '', url: 'asd' },
|
||||
xLink: { __typename: 'Link', label: '', url: 'asd' },
|
||||
company: {
|
||||
__typename: 'Company',
|
||||
domainName: 'linkedin.com',
|
||||
name: 'Linkedin',
|
||||
employees: 10102,
|
||||
accountOwnerId: null,
|
||||
address: '',
|
||||
idealCustomerProfile: false,
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-3ec3-4fe3-8997-b76aa0bfa408',
|
||||
position: 1,
|
||||
updatedAt: '2024-05-23T13:21:41.159Z',
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
linkedinLink: { __typename: 'Link', label: 'adasd', url: 'adasd' },
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'Person',
|
||||
city: 'Los Angeles',
|
||||
jobTitle: '@',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-ac73-4797-824e-87a1f5aea9e0',
|
||||
email: 'sylvie.palmer@linkedin.com',
|
||||
phone: '+33780123456',
|
||||
position: 2,
|
||||
name: { __typename: 'FullName', firstName: 'Sylvie', lastName: 'Palmer' },
|
||||
linkedinLink: { __typename: 'Link', label: '', url: '' },
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
company: {
|
||||
__typename: 'Company',
|
||||
domainName: 'algolia.com',
|
||||
name: 'Algolia',
|
||||
employees: 10000,
|
||||
accountOwnerId: '20202020-77d5-4cb6-b60a-f4a835a85d61',
|
||||
address: '',
|
||||
idealCustomerProfile: false,
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-1455-4c57-afaf-dd5dc086361d',
|
||||
position: 13,
|
||||
updatedAt: '2024-05-28T15:52:31.839Z',
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
linkedinLink: { __typename: 'Link', label: '', url: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'Person',
|
||||
city: 'Seattle',
|
||||
jobTitle: '',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-f517-42fd-80ae-14173b3b70ae',
|
||||
email: 'christopher.gonzalez@qonto.com',
|
||||
phone: '+33789012345',
|
||||
position: 3,
|
||||
name: {
|
||||
__typename: 'FullName',
|
||||
firstName: 'Christopher',
|
||||
lastName: 'Gonzalez',
|
||||
},
|
||||
linkedinLink: { __typename: 'Link', label: '', url: '' },
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
company: {
|
||||
__typename: 'Company',
|
||||
domainName: 'qonto.com',
|
||||
name: 'Qonto',
|
||||
employees: 123123123,
|
||||
accountOwnerId: '20202020-1553-45c6-a028-5a9064cce07f',
|
||||
address: '',
|
||||
idealCustomerProfile: false,
|
||||
createdAt: '2024-05-08T13:16:29.000Z',
|
||||
id: '20202020-0713-40a5-8216-82802401d33e',
|
||||
position: 9.5,
|
||||
updatedAt: '2024-05-28T15:52:46.961Z',
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
linkedinLink: { __typename: 'Link', label: '', url: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'Person',
|
||||
city: 'Los Angeles',
|
||||
jobTitle: '',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-eee1-4690-ad2c-8619e5b56a2e',
|
||||
email: 'ashley.parker@qonto.com',
|
||||
phone: '+33780123456',
|
||||
position: 4,
|
||||
name: { __typename: 'FullName', firstName: 'Ashley', lastName: 'Parker' },
|
||||
linkedinLink: { __typename: 'Link', label: '', url: '' },
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
company: {
|
||||
__typename: 'Company',
|
||||
domainName: 'qonto.com',
|
||||
name: 'Qonto',
|
||||
employees: 123123123,
|
||||
accountOwnerId: '20202020-1553-45c6-a028-5a9064cce07f',
|
||||
address: '',
|
||||
idealCustomerProfile: false,
|
||||
createdAt: '2024-05-08T13:16:29.000Z',
|
||||
id: '20202020-0713-40a5-8216-82802401d33e',
|
||||
position: 9.5,
|
||||
updatedAt: '2024-05-28T15:52:46.961Z',
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
linkedinLink: { __typename: 'Link', label: '', url: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'Person',
|
||||
city: 'Seattle',
|
||||
jobTitle: '',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-6784-4449-afdf-dc62cb8702f2',
|
||||
email: 'nicholas.wright@microsoft.com',
|
||||
phone: '+33781234567',
|
||||
position: 5,
|
||||
name: { __typename: 'FullName', firstName: 'Nicholas', lastName: 'Wright' },
|
||||
linkedinLink: { __typename: 'Link', label: '', url: '' },
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
company: {
|
||||
__typename: 'Company',
|
||||
domainName: 'microsoft.com',
|
||||
name: 'Microsoft',
|
||||
employees: 10000,
|
||||
accountOwnerId: '20202020-0687-4c41-b707-ed1bfca972a7',
|
||||
address: '',
|
||||
idealCustomerProfile: false,
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-ed89-413a-b31a-962986e67bb4',
|
||||
position: 6.09375,
|
||||
updatedAt: '2024-05-28T15:52:35.621Z',
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: 10000000000,
|
||||
currencyCode: 'USD',
|
||||
},
|
||||
linkedinLink: { __typename: 'Link', label: '', url: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'Person',
|
||||
city: 'New York',
|
||||
jobTitle: '',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-490f-4466-8391-733cfd66a0c8',
|
||||
email: 'isabella.scott@microsoft.com',
|
||||
phone: '+33782345678',
|
||||
position: 6,
|
||||
name: { __typename: 'FullName', firstName: 'Isabella', lastName: 'Scott' },
|
||||
linkedinLink: { __typename: 'Link', label: '', url: '' },
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
company: {
|
||||
__typename: 'Company',
|
||||
domainName: 'microsoft.com',
|
||||
name: 'Microsoft',
|
||||
employees: 10000,
|
||||
accountOwnerId: '20202020-0687-4c41-b707-ed1bfca972a7',
|
||||
address: '',
|
||||
idealCustomerProfile: false,
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-ed89-413a-b31a-962986e67bb4',
|
||||
position: 6.09375,
|
||||
updatedAt: '2024-05-28T15:52:35.621Z',
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: 10000000000,
|
||||
currencyCode: 'USD',
|
||||
},
|
||||
linkedinLink: { __typename: 'Link', label: '', url: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'Person',
|
||||
city: 'Seattle',
|
||||
jobTitle: '',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-80f1-4dff-b570-a74942528de3',
|
||||
email: 'matthew.green@microsoft.com',
|
||||
phone: '+33783456789',
|
||||
position: 7,
|
||||
name: { __typename: 'FullName', firstName: 'Matthew', lastName: 'Green' },
|
||||
linkedinLink: { __typename: 'Link', label: '', url: '' },
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
company: {
|
||||
__typename: 'Company',
|
||||
domainName: 'microsoft.com',
|
||||
name: 'Microsoft',
|
||||
employees: 10000,
|
||||
accountOwnerId: '20202020-0687-4c41-b707-ed1bfca972a7',
|
||||
address: '',
|
||||
idealCustomerProfile: false,
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-ed89-413a-b31a-962986e67bb4',
|
||||
position: 6.09375,
|
||||
updatedAt: '2024-05-28T15:52:35.621Z',
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: 10000000000,
|
||||
currencyCode: 'USD',
|
||||
},
|
||||
linkedinLink: { __typename: 'Link', label: '', url: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'Person',
|
||||
city: 'New York',
|
||||
jobTitle: '',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-338b-46df-8811-fa08c7d19d35',
|
||||
email: 'elizabeth.baker@airbnb.com',
|
||||
phone: '+33784567890',
|
||||
position: 8,
|
||||
name: { __typename: 'FullName', firstName: 'Elizabeth', lastName: 'Baker' },
|
||||
linkedinLink: { __typename: 'Link', label: '', url: '' },
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
company: {
|
||||
__typename: 'Company',
|
||||
domainName: 'airbnb.com',
|
||||
name: 'Airbnb',
|
||||
employees: 123333,
|
||||
accountOwnerId: null,
|
||||
address: '',
|
||||
idealCustomerProfile: false,
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-171e-4bcc-9cf7-43448d6fb278',
|
||||
position: 5,
|
||||
updatedAt: '2024-05-28T15:52:27.902Z',
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
linkedinLink: { __typename: 'Link', label: '', url: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'Person',
|
||||
city: 'San Francisco',
|
||||
jobTitle: '',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-64ad-4b0e-bbfd-e9fd795b7016',
|
||||
email: 'christopher.nelson@airbnb.com',
|
||||
phone: '+33785678901',
|
||||
position: 9,
|
||||
name: {
|
||||
__typename: 'FullName',
|
||||
firstName: 'Christopher',
|
||||
lastName: 'Nelson',
|
||||
},
|
||||
linkedinLink: { __typename: 'Link', label: '', url: '' },
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
company: {
|
||||
__typename: 'Company',
|
||||
domainName: 'airbnb.com',
|
||||
name: 'Airbnb',
|
||||
employees: 123333,
|
||||
accountOwnerId: null,
|
||||
address: '',
|
||||
idealCustomerProfile: false,
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-171e-4bcc-9cf7-43448d6fb278',
|
||||
position: 5,
|
||||
updatedAt: '2024-05-28T15:52:27.902Z',
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
linkedinLink: { __typename: 'Link', label: '', url: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'Person',
|
||||
city: 'New York',
|
||||
jobTitle: '',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-5d54-41b7-ba36-f0d20e1417ae',
|
||||
email: 'avery.carter@airbnb.com',
|
||||
phone: '+33786789012',
|
||||
position: 10,
|
||||
name: { __typename: 'FullName', firstName: 'Avery', lastName: 'Carter' },
|
||||
linkedinLink: { __typename: 'Link', label: '', url: '' },
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
company: {
|
||||
__typename: 'Company',
|
||||
domainName: 'airbnb.com',
|
||||
name: 'Airbnb',
|
||||
employees: 123333,
|
||||
accountOwnerId: null,
|
||||
address: '',
|
||||
idealCustomerProfile: false,
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-171e-4bcc-9cf7-43448d6fb278',
|
||||
position: 5,
|
||||
updatedAt: '2024-05-28T15:52:27.902Z',
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: null,
|
||||
currencyCode: '',
|
||||
},
|
||||
linkedinLink: { __typename: 'Link', label: '', url: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'Person',
|
||||
city: 'Los Angeles',
|
||||
jobTitle: '',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-623d-41fe-92e7-dd45b7c568e1',
|
||||
email: 'ethan.mitchell@google.com',
|
||||
phone: '+33787890123',
|
||||
position: 11,
|
||||
name: { __typename: 'FullName', firstName: 'Ethan', lastName: 'Mitchell' },
|
||||
linkedinLink: { __typename: 'Link', label: '', url: '' },
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
company: {
|
||||
__typename: 'Company',
|
||||
domainName: 'google.com',
|
||||
name: 'Google',
|
||||
employees: 10202,
|
||||
accountOwnerId: '20202020-0687-4c41-b707-ed1bfca972a7',
|
||||
address: 'Paris France',
|
||||
idealCustomerProfile: false,
|
||||
createdAt: '2024-05-21T13:16:29.000Z',
|
||||
id: '20202020-c21e-4ec2-873b-de4264d89025',
|
||||
position: 7.5,
|
||||
updatedAt: '2024-05-28T15:53:28.838Z',
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: 1001000000,
|
||||
currencyCode: 'USD',
|
||||
},
|
||||
linkedinLink: { __typename: 'Link', label: '', url: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'Person',
|
||||
city: 'Seattle',
|
||||
jobTitle: '',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-2d40-4e49-8df4-9c6a049190ef',
|
||||
email: 'madison.perez@google.com',
|
||||
phone: '+33788901234',
|
||||
position: 12,
|
||||
name: { __typename: 'FullName', firstName: 'Madison', lastName: 'Perez' },
|
||||
linkedinLink: { __typename: 'Link', label: '', url: '' },
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
company: {
|
||||
__typename: 'Company',
|
||||
domainName: 'google.com',
|
||||
name: 'Google',
|
||||
employees: 10202,
|
||||
accountOwnerId: '20202020-0687-4c41-b707-ed1bfca972a7',
|
||||
address: 'Paris France',
|
||||
idealCustomerProfile: false,
|
||||
createdAt: '2024-05-21T13:16:29.000Z',
|
||||
id: '20202020-c21e-4ec2-873b-de4264d89025',
|
||||
position: 7.5,
|
||||
updatedAt: '2024-05-28T15:53:28.838Z',
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: 1001000000,
|
||||
currencyCode: 'USD',
|
||||
},
|
||||
linkedinLink: { __typename: 'Link', label: '', url: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'Person',
|
||||
city: 'Seattle',
|
||||
jobTitle: '',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-2d40-4e49-8df4-9c6a049190df',
|
||||
email: 'bertrand.voulzy@google.com',
|
||||
phone: '+33788901234',
|
||||
position: 13,
|
||||
name: { __typename: 'FullName', firstName: 'Bertrand', lastName: 'Voulzy' },
|
||||
linkedinLink: { __typename: 'Link', label: '', url: '' },
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
company: {
|
||||
__typename: 'Company',
|
||||
domainName: 'google.com',
|
||||
name: 'Google',
|
||||
employees: 10202,
|
||||
accountOwnerId: '20202020-0687-4c41-b707-ed1bfca972a7',
|
||||
address: 'Paris France',
|
||||
idealCustomerProfile: false,
|
||||
createdAt: '2024-05-21T13:16:29.000Z',
|
||||
id: '20202020-c21e-4ec2-873b-de4264d89025',
|
||||
position: 7.5,
|
||||
updatedAt: '2024-05-28T15:53:28.838Z',
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: 1001000000,
|
||||
currencyCode: 'USD',
|
||||
},
|
||||
linkedinLink: { __typename: 'Link', label: '', url: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'Person',
|
||||
city: 'Seattle',
|
||||
jobTitle: '',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-2d40-4e49-8df4-9c6a049191de',
|
||||
email: 'louis.duss@google.com',
|
||||
phone: '+33788901234',
|
||||
position: 14,
|
||||
name: { __typename: 'FullName', firstName: 'Louis', lastName: 'Duss' },
|
||||
linkedinLink: { __typename: 'Link', label: '', url: '' },
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
company: {
|
||||
__typename: 'Company',
|
||||
domainName: 'google.com',
|
||||
name: 'Google',
|
||||
employees: 10202,
|
||||
accountOwnerId: '20202020-0687-4c41-b707-ed1bfca972a7',
|
||||
address: 'Paris France',
|
||||
idealCustomerProfile: false,
|
||||
createdAt: '2024-05-21T13:16:29.000Z',
|
||||
id: '20202020-c21e-4ec2-873b-de4264d89025',
|
||||
position: 7.5,
|
||||
updatedAt: '2024-05-28T15:53:28.838Z',
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: 1001000000,
|
||||
currencyCode: 'USD',
|
||||
},
|
||||
linkedinLink: { __typename: 'Link', label: '', url: '' },
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'Person',
|
||||
city: 'Seattle',
|
||||
jobTitle: '',
|
||||
createdAt: '2024-05-01T13:16:29.046Z',
|
||||
id: '20202020-2d40-4e49-8df4-9c6a049191df',
|
||||
email: 'lorie.vladim@google.com',
|
||||
phone: '+33788901235',
|
||||
position: 15,
|
||||
name: { __typename: 'FullName', firstName: 'Lorie', lastName: 'Vladim' },
|
||||
linkedinLink: { __typename: 'Link', label: '', url: '' },
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
company: {
|
||||
__typename: 'Company',
|
||||
domainName: 'google.com',
|
||||
name: 'Google',
|
||||
employees: 10202,
|
||||
accountOwnerId: '20202020-0687-4c41-b707-ed1bfca972a7',
|
||||
address: 'Paris France',
|
||||
idealCustomerProfile: false,
|
||||
createdAt: '2024-05-21T13:16:29.000Z',
|
||||
id: '20202020-c21e-4ec2-873b-de4264d89025',
|
||||
position: 7.5,
|
||||
updatedAt: '2024-05-28T15:53:28.838Z',
|
||||
xLink: { __typename: 'Link', label: '', url: '' },
|
||||
annualRecurringRevenue: {
|
||||
__typename: 'Currency',
|
||||
amountMicros: 1001000000,
|
||||
currencyCode: 'USD',
|
||||
},
|
||||
linkedinLink: { __typename: 'Link', label: '', url: '' },
|
||||
},
|
||||
},
|
||||
];
|
@ -2,6 +2,9 @@
|
||||
import { isDate, isNumber, isString } from '@sniptt/guards';
|
||||
import { differenceInCalendarDays, formatDistanceToNow } from 'date-fns';
|
||||
import { DateTime } from 'luxon';
|
||||
import moize from 'moize';
|
||||
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { logError } from './logError';
|
||||
|
||||
@ -133,3 +136,75 @@ export const beautifyDateDiff = (
|
||||
if (![0, 1].includes(dateDiff.days)) result = result + 's';
|
||||
return result;
|
||||
};
|
||||
|
||||
const getMonthLabels = () => {
|
||||
const formatter = new Intl.DateTimeFormat(undefined, {
|
||||
month: 'short',
|
||||
timeZone: 'UTC',
|
||||
});
|
||||
|
||||
return [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
||||
.map((month) => {
|
||||
const monthZeroFilled = month < 10 ? `0${month}` : month;
|
||||
return new Date(`2017-${monthZeroFilled}-01T00:00:00+00:00`);
|
||||
})
|
||||
.map((date) => formatter.format(date));
|
||||
};
|
||||
|
||||
const getMonthLabelsMemoized = moize(getMonthLabels);
|
||||
|
||||
export const formatISOStringToHumanReadableDateTime = (date: string) => {
|
||||
const monthLabels = getMonthLabelsMemoized();
|
||||
|
||||
if (!isDefined(monthLabels)) {
|
||||
return formatToHumanReadableDateTime(date);
|
||||
}
|
||||
|
||||
const year = date.slice(0, 4);
|
||||
const month = date.slice(5, 7);
|
||||
const day = date.slice(8, 10);
|
||||
|
||||
const monthLabel = monthLabels[parseInt(month, 10) - 1];
|
||||
|
||||
const jsDate = new Date(date);
|
||||
|
||||
return `${day} ${monthLabel} ${year} - ${jsDate.getHours()}:${jsDate.getMinutes()}`;
|
||||
};
|
||||
|
||||
export const formatISOStringToHumanReadableDate = (date: string) => {
|
||||
const monthLabels = getMonthLabelsMemoized();
|
||||
|
||||
if (!isDefined(monthLabels)) {
|
||||
return formatToHumanReadableDate(date);
|
||||
}
|
||||
|
||||
const year = date.slice(0, 4);
|
||||
const month = date.slice(5, 7);
|
||||
const day = date.slice(8, 10);
|
||||
|
||||
const monthLabel = monthLabels[parseInt(month, 10) - 1];
|
||||
|
||||
return `${day} ${monthLabel} ${year}`;
|
||||
};
|
||||
|
||||
export const formatToHumanReadableDate = (date: Date | string) => {
|
||||
const parsedJSDate = parseDate(date).toJSDate();
|
||||
|
||||
return new Intl.DateTimeFormat(undefined, {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
}).format(parsedJSDate);
|
||||
};
|
||||
|
||||
export const formatToHumanReadableDateTime = (date: Date | string) => {
|
||||
const parsedJSDate = parseDate(date).toJSDate();
|
||||
|
||||
return new Intl.DateTimeFormat(undefined, {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
}).format(parsedJSDate);
|
||||
};
|
||||
|
@ -1,27 +1,3 @@
|
||||
import { parseDate } from './date-utils';
|
||||
|
||||
export const formatToHumanReadableDate = (date: Date | string) => {
|
||||
const parsedJSDate = parseDate(date).toJSDate();
|
||||
|
||||
return new Intl.DateTimeFormat(undefined, {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
}).format(parsedJSDate);
|
||||
};
|
||||
|
||||
export const formatToHumanReadableDateTime = (date: Date | string) => {
|
||||
const parsedJSDate = parseDate(date).toJSDate();
|
||||
|
||||
return new Intl.DateTimeFormat(undefined, {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
}).format(parsedJSDate);
|
||||
};
|
||||
|
||||
export const sanitizeURL = (link: string | null | undefined) => {
|
||||
return link
|
||||
? link.replace(/(https?:\/\/)|(www\.)/g, '').replace(/\/$/, '')
|
||||
|
@ -49,8 +49,21 @@ export default defineConfig(({ command, mode }) => {
|
||||
}),
|
||||
svgr(),
|
||||
checker(checkers),
|
||||
// TODO: fix this, we have to restrict the include to only the components that are using linaria
|
||||
// Otherwise the build will fail because wyw tries to include emotion styled components
|
||||
wyw({
|
||||
include: ['**/EllipsisDisplay.tsx', '**/ContactLink.tsx'],
|
||||
include: [
|
||||
'**/CurrencyDisplay.tsx',
|
||||
'**/EllipsisDisplay.tsx',
|
||||
'**/ContactLink.tsx',
|
||||
'**/BooleanDisplay.tsx',
|
||||
'**/LinksDisplay.tsx',
|
||||
'**/RoundedLink.tsx',
|
||||
'**/OverflowingTextWithTooltip.tsx',
|
||||
'**/Chip.tsx',
|
||||
'**/Tag.tsx',
|
||||
'**/MultiSelectFieldDisplay.tsx',
|
||||
],
|
||||
babelOptions: {
|
||||
presets: ['@babel/preset-typescript', '@babel/preset-react'],
|
||||
},
|
||||
|
@ -3,7 +3,11 @@ import { ThemeProvider } from '@emotion/react';
|
||||
import { Preview } from '@storybook/react';
|
||||
import { useDarkMode } from 'storybook-dark-mode';
|
||||
|
||||
import { THEME_DARK, THEME_LIGHT } from '../src/theme/index';
|
||||
import {
|
||||
THEME_DARK,
|
||||
THEME_LIGHT,
|
||||
ThemeContextProvider,
|
||||
} from '../src/theme/index';
|
||||
|
||||
const preview: Preview = {
|
||||
decorators: [
|
||||
@ -18,7 +22,9 @@ const preview: Preview = {
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
<Story />
|
||||
<ThemeContextProvider theme={theme}>
|
||||
<Story />
|
||||
</ThemeContextProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
},
|
||||
|
@ -1,84 +0,0 @@
|
||||
.label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chip {
|
||||
--chip-horizontal-padding: calc(var(--twentycrm-spacing-multiplicator) * 1px);
|
||||
--chip-vertical-padding: calc(var(--twentycrm-spacing-multiplicator) * 1px);
|
||||
|
||||
align-items: center;
|
||||
border-radius: var(--twentycrm-border-radius-sm);
|
||||
|
||||
color: var(--twentycrm-font-color-secondary);
|
||||
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
|
||||
gap: calc(var(--twentycrm-spacing-multiplicator) * 1px);
|
||||
height: calc(var(--twentycrm-spacing-multiplicator) * 3px);
|
||||
|
||||
max-width: calc(100% - var(--chip-horizontal-padding) * 2px);
|
||||
overflow: hidden;
|
||||
|
||||
padding: var(--chip-vertical-padding) var(--chip-horizontal-padding);
|
||||
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
cursor: not-allowed;
|
||||
|
||||
color: var(--twentycrm-font-color-light);
|
||||
|
||||
}
|
||||
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.accent-text-primary {
|
||||
color: var(--twentycrm-font-color-primary);
|
||||
}
|
||||
|
||||
.accent-text-secondary {
|
||||
font-weight: var(--twentycrm-font-weight-medium);
|
||||
}
|
||||
|
||||
.size-large {
|
||||
height: calc(var(--twentycrm-spacing-multiplicator) * 4px);
|
||||
}
|
||||
|
||||
.variant-regular:hover {
|
||||
background-color: var(--twentycrm-background-transparent-light);
|
||||
}
|
||||
|
||||
.variant-regular:active {
|
||||
background-color: var(--twentycrm-background-transparent-medium);
|
||||
}
|
||||
|
||||
.variant-highlighted {
|
||||
background-color: var(--twentycrm-background-transparent-light);
|
||||
}
|
||||
|
||||
.variant-highlighted:hover {
|
||||
background-color: var(--twentycrm-background-transparent-medium);
|
||||
}
|
||||
|
||||
.variant-highlighted:active {
|
||||
background-color: var(--twentycrm-background-transparent-strong);
|
||||
}
|
||||
|
||||
.variant-rounded {
|
||||
--chip-horizontal-padding: calc(var(--twentycrm-spacing-multiplicator) * 2px);
|
||||
--chip-vertical-padding: 1px;
|
||||
|
||||
background-color: var(--twentycrm-background-transparent-light);
|
||||
border: 1px solid var(--twentycrm-border-color-medium);
|
||||
border-radius: 50px;
|
||||
}
|
||||
|
||||
.variant-transparent {
|
||||
cursor: inherit;
|
||||
}
|
@ -1,10 +1,9 @@
|
||||
import { MouseEvent, ReactNode } from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
import { Theme, withTheme } from '@emotion/react';
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
import { OverflowingTextWithTooltip } from '@ui/display/tooltip/OverflowingTextWithTooltip';
|
||||
|
||||
import styles from './Chip.module.css';
|
||||
|
||||
export enum ChipSize {
|
||||
Large = 'large',
|
||||
Small = 'small',
|
||||
@ -34,9 +33,86 @@ type ChipProps = {
|
||||
rightComponent?: ReactNode;
|
||||
className?: string;
|
||||
onClick?: (event: MouseEvent<HTMLDivElement>) => void;
|
||||
to?: string;
|
||||
};
|
||||
|
||||
const StyledContainer = withTheme(styled.div<
|
||||
Pick<
|
||||
ChipProps,
|
||||
'accent' | 'clickable' | 'disabled' | 'maxWidth' | 'size' | 'variant'
|
||||
> & { theme: Theme }
|
||||
>`
|
||||
--chip-horizontal-padding: ${({ theme }) => theme.spacing(1)};
|
||||
--chip-vertical-padding: ${({ theme }) => theme.spacing(1)};
|
||||
|
||||
text-decoration: none;
|
||||
align-items: center;
|
||||
|
||||
color: ${({ theme, accent, disabled }) =>
|
||||
disabled
|
||||
? theme.font.color.light
|
||||
: accent === ChipAccent.TextPrimary
|
||||
? theme.font.color.primary
|
||||
: theme.font.color.secondary};
|
||||
|
||||
cursor: ${({ clickable, disabled, variant }) =>
|
||||
variant === ChipVariant.Transparent
|
||||
? 'inherit'
|
||||
: clickable
|
||||
? 'pointer'
|
||||
: disabled
|
||||
? 'not-allowed'
|
||||
: 'inherit'};
|
||||
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
height: ${({ theme }) => theme.spacing(3)};
|
||||
max-width: ${({ maxWidth }) =>
|
||||
maxWidth
|
||||
? `calc(${maxWidth}px - 2 * var(--chip-horizontal-padding))`
|
||||
: '200px'};
|
||||
|
||||
overflow: hidden;
|
||||
padding: var(--chip-vertical-padding) var(--chip-horizontal-padding);
|
||||
user-select: none;
|
||||
|
||||
font-weight: ${({ theme, accent }) =>
|
||||
accent === ChipAccent.TextSecondary ? theme.font.weight.medium : 'inherit'};
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ theme, variant, disabled }) =>
|
||||
variant === ChipVariant.Regular && !disabled
|
||||
? theme.background.transparent.light
|
||||
: variant === ChipVariant.Highlighted
|
||||
? theme.background.transparent.medium
|
||||
: 'inherit'};
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: ${({ theme, disabled, variant }) =>
|
||||
variant === ChipVariant.Regular && !disabled
|
||||
? theme.background.transparent.medium
|
||||
: variant === ChipVariant.Highlighted
|
||||
? theme.background.transparent.strong
|
||||
: 'inherit'};
|
||||
}
|
||||
|
||||
background-color: ${({ theme, variant }) =>
|
||||
variant === ChipVariant.Highlighted
|
||||
? theme.background.transparent.light
|
||||
: variant === ChipVariant.Rounded
|
||||
? theme.background.transparent.lighter
|
||||
: 'inherit'};
|
||||
|
||||
border: ${({ theme, variant }) =>
|
||||
variant === ChipVariant.Rounded
|
||||
? `1px solid ${theme.border.color.medium}`
|
||||
: 'none'};
|
||||
|
||||
border-radius: ${({ theme, variant }) =>
|
||||
variant === ChipVariant.Rounded ? '50px' : theme.border.radius.sm};
|
||||
`);
|
||||
|
||||
export const Chip = ({
|
||||
size = ChipSize.Small,
|
||||
label,
|
||||
@ -49,30 +125,21 @@ export const Chip = ({
|
||||
onClick,
|
||||
}: ChipProps) => {
|
||||
return (
|
||||
<div
|
||||
<StyledContainer
|
||||
data-testid="chip"
|
||||
className={clsx({
|
||||
[styles.chip]: true,
|
||||
[styles.clickable]: clickable,
|
||||
[styles.disabled]: disabled,
|
||||
[styles.accentTextPrimary]: accent === ChipAccent.TextPrimary,
|
||||
[styles.accentTextSecondary]: accent === ChipAccent.TextSecondary,
|
||||
[styles.sizeLarge]: size === ChipSize.Large,
|
||||
[styles.variantRegular]: variant === ChipVariant.Regular,
|
||||
[styles.variantHighlighted]: variant === ChipVariant.Highlighted,
|
||||
[styles.variantRounded]: variant === ChipVariant.Rounded,
|
||||
[styles.variantTransparent]: variant === ChipVariant.Transparent,
|
||||
})}
|
||||
accent={accent}
|
||||
clickable={clickable}
|
||||
disabled={disabled}
|
||||
size={size}
|
||||
variant={variant}
|
||||
onClick={onClick}
|
||||
>
|
||||
{leftComponent}
|
||||
<div className={styles.label}>
|
||||
<OverflowingTextWithTooltip
|
||||
size={size === ChipSize.Large ? 'large' : 'small'}
|
||||
text={label}
|
||||
/>
|
||||
</div>
|
||||
<OverflowingTextWithTooltip
|
||||
size={size === ChipSize.Large ? 'large' : 'small'}
|
||||
text={label}
|
||||
/>
|
||||
{rightComponent}
|
||||
</div>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
@ -1,16 +1,28 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useContext } from 'react';
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
import { IconComponent, OverflowingTextWithTooltip } from '@ui/display';
|
||||
import { ThemeColor, themeColorSchema } from '@ui/theme';
|
||||
import {
|
||||
BORDER_COMMON,
|
||||
THEME_COMMON,
|
||||
ThemeColor,
|
||||
ThemeContext,
|
||||
ThemeType,
|
||||
} from '@ui/theme';
|
||||
|
||||
const spacing5 = THEME_COMMON.spacing(5);
|
||||
const spacing2 = THEME_COMMON.spacing(2);
|
||||
const spacing1 = THEME_COMMON.spacing(1);
|
||||
|
||||
const StyledTag = styled.h3<{
|
||||
theme: ThemeType;
|
||||
color: ThemeColor;
|
||||
weight: TagWeight;
|
||||
preventShrink?: boolean;
|
||||
}>`
|
||||
align-items: center;
|
||||
background: ${({ color, theme }) => theme.tag.background[color]};
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
border-radius: ${BORDER_COMMON.radius.sm};
|
||||
color: ${({ color, theme }) => theme.tag.text[color]};
|
||||
display: inline-flex;
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
@ -19,10 +31,15 @@ const StyledTag = styled.h3<{
|
||||
weight === 'regular'
|
||||
? theme.font.weight.regular
|
||||
: theme.font.weight.medium};
|
||||
height: ${({ theme }) => theme.spacing(5)};
|
||||
height: ${spacing5};
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
padding: 0 ${({ theme }) => theme.spacing(2)};
|
||||
padding: 0 ${spacing2};
|
||||
|
||||
gap: ${spacing1};
|
||||
|
||||
min-width: ${({ preventShrink }) =>
|
||||
preventShrink ? 'fit-content' : 'none;'};
|
||||
`;
|
||||
|
||||
const StyledContent = styled.span`
|
||||
@ -31,9 +48,13 @@ const StyledContent = styled.span`
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
const StyledNonShrinkableText = styled.span`
|
||||
white-space: nowrap;
|
||||
width: fit-content;
|
||||
`;
|
||||
|
||||
const StyledIconContainer = styled.div`
|
||||
display: flex;
|
||||
margin-right: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
type TagWeight = 'regular' | 'medium';
|
||||
@ -45,8 +66,10 @@ type TagProps = {
|
||||
Icon?: IconComponent;
|
||||
onClick?: () => void;
|
||||
weight?: TagWeight;
|
||||
preventShrink?: boolean;
|
||||
};
|
||||
|
||||
// TODO: Find a way to have ellipsis and shrinkable tag in tag list while keeping good perf for table cells
|
||||
export const Tag = ({
|
||||
className,
|
||||
color,
|
||||
@ -54,23 +77,31 @@ export const Tag = ({
|
||||
Icon,
|
||||
onClick,
|
||||
weight = 'regular',
|
||||
preventShrink,
|
||||
}: TagProps) => {
|
||||
const theme = useTheme();
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<StyledTag
|
||||
theme={theme}
|
||||
className={className}
|
||||
color={themeColorSchema.catch('gray').parse(color)}
|
||||
color={color}
|
||||
onClick={onClick}
|
||||
weight={weight}
|
||||
preventShrink={preventShrink}
|
||||
>
|
||||
{!!Icon && (
|
||||
<StyledIconContainer>
|
||||
<Icon size={theme.icon.size.sm} stroke={theme.icon.stroke.sm} />
|
||||
</StyledIconContainer>
|
||||
)}
|
||||
<StyledContent>
|
||||
<OverflowingTextWithTooltip text={text} />
|
||||
</StyledContent>
|
||||
{preventShrink ? (
|
||||
<StyledNonShrinkableText>{text}</StyledNonShrinkableText>
|
||||
) : (
|
||||
<StyledContent>
|
||||
<OverflowingTextWithTooltip text={text} />
|
||||
</StyledContent>
|
||||
)}
|
||||
</StyledTag>
|
||||
);
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { expect, fn, userEvent, within } from '@storybook/test';
|
||||
|
||||
import { IconUser } from '@ui/display/icon/components/TablerIcons';
|
||||
import {
|
||||
CatalogDecorator,
|
||||
CatalogStory,
|
||||
@ -48,6 +49,30 @@ export const WithLongText: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const WithIcon: Story = {
|
||||
decorators: [ComponentDecorator],
|
||||
args: {
|
||||
color: 'green',
|
||||
text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
|
||||
Icon: IconUser,
|
||||
},
|
||||
parameters: {
|
||||
container: { width: 100 },
|
||||
},
|
||||
};
|
||||
|
||||
export const DontShrink: Story = {
|
||||
decorators: [ComponentDecorator],
|
||||
args: {
|
||||
color: 'green',
|
||||
text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
|
||||
preventShrink: true,
|
||||
},
|
||||
parameters: {
|
||||
container: { width: 100 },
|
||||
},
|
||||
};
|
||||
|
||||
export const Catalog: CatalogStory<Story, typeof Tag> = {
|
||||
argTypes: {
|
||||
color: { control: false },
|
||||
|
@ -1,11 +1,38 @@
|
||||
import { useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import clsx from 'clsx';
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
import { THEME_COMMON } from '@ui/theme';
|
||||
|
||||
import { AppTooltip } from './AppTooltip';
|
||||
|
||||
import styles from './OverflowingTextWithTooltip.module.css';
|
||||
const spacing4 = THEME_COMMON.spacing(4);
|
||||
|
||||
const StyledOverflowingText = styled.div<{
|
||||
cursorPointer: boolean;
|
||||
size: 'large' | 'small';
|
||||
}>`
|
||||
cursor: ${({ cursorPointer }) => (cursorPointer ? 'pointer' : 'inherit')};
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
|
||||
font-weight: inherit;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-decoration: inherit;
|
||||
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
height: ${({ size }) => (size === 'large' ? spacing4 : 'auto')};
|
||||
|
||||
& :hover {
|
||||
text-overflow: ${({ cursorPointer }) =>
|
||||
cursorPointer ? 'clip' : 'ellipsis'};
|
||||
white-space: ${({ cursorPointer }) =>
|
||||
cursorPointer ? 'normal' : 'nowrap'};
|
||||
}
|
||||
`;
|
||||
|
||||
export const OverflowingTextWithTooltip = ({
|
||||
size = 'small',
|
||||
@ -16,7 +43,7 @@ export const OverflowingTextWithTooltip = ({
|
||||
text: string | null | undefined;
|
||||
mutliline?: boolean;
|
||||
}) => {
|
||||
const textElementId = `title-id-${uuidV4()}`;
|
||||
const textElementId = `title-id-${+new Date()}`;
|
||||
|
||||
const textRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@ -43,20 +70,17 @@ export const OverflowingTextWithTooltip = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
<StyledOverflowingText
|
||||
data-testid="tooltip"
|
||||
className={clsx({
|
||||
[styles.main]: true,
|
||||
[styles.cursor]: isTitleOverflowing,
|
||||
[styles.large]: size === 'large',
|
||||
})}
|
||||
cursorPointer={isTitleOverflowing}
|
||||
size={size}
|
||||
ref={textRef}
|
||||
id={textElementId}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
</StyledOverflowingText>
|
||||
{isTitleOverflowing &&
|
||||
createPortal(
|
||||
<div onClick={handleTooltipClick}>
|
||||
|
@ -34,6 +34,7 @@ export * from './constants/TextInputStyle';
|
||||
export * from './constants/ThemeCommon';
|
||||
export * from './constants/ThemeDark';
|
||||
export * from './constants/ThemeLight';
|
||||
export * from './provider/ThemeContextProvider';
|
||||
export * from './provider/ThemeProvider';
|
||||
export * from './types/ThemeType';
|
||||
export * from './utils/getNextThemeColor';
|
||||
|
@ -0,0 +1,23 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
import { ThemeType } from '@ui/theme/types/ThemeType';
|
||||
|
||||
export type ThemeContextType = {
|
||||
theme: ThemeType;
|
||||
};
|
||||
|
||||
export const ThemeContext = createContext<ThemeContextType>(
|
||||
{} as ThemeContextType,
|
||||
);
|
||||
|
||||
export const ThemeContextProvider = ({
|
||||
children,
|
||||
theme,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
theme: ThemeType;
|
||||
}) => {
|
||||
return (
|
||||
<ThemeContext.Provider value={{ theme }}>{children}</ThemeContext.Provider>
|
||||
);
|
||||
};
|
@ -1,6 +1,8 @@
|
||||
import { ReactNode, useEffect } from 'react';
|
||||
import { ThemeProvider as EmotionThemeProvider } from '@emotion/react';
|
||||
|
||||
import { ThemeContextProvider } from '@ui/theme/provider/ThemeContextProvider';
|
||||
|
||||
import { ThemeType } from '..';
|
||||
|
||||
import './theme.css';
|
||||
@ -16,7 +18,11 @@ const ThemeProvider = ({ theme, children }: ThemeProviderProps) => {
|
||||
theme.name === 'dark' ? 'dark' : 'light';
|
||||
}, [theme]);
|
||||
|
||||
return <EmotionThemeProvider theme={theme}>{children}</EmotionThemeProvider>;
|
||||
return (
|
||||
<EmotionThemeProvider theme={theme}>
|
||||
<ThemeContextProvider theme={theme}>{children}</ThemeContextProvider>
|
||||
</EmotionThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemeProvider;
|
||||
|
@ -1,5 +1,6 @@
|
||||
/// <reference types='vitest' />
|
||||
import react from '@vitejs/plugin-react-swc';
|
||||
import wyw from '@wyw-in-js/vite';
|
||||
import * as path from 'path';
|
||||
import { defineConfig } from 'vite';
|
||||
import checker from 'vite-plugin-checker';
|
||||
@ -27,6 +28,16 @@ export default defineConfig({
|
||||
tsconfigPath: path.join(__dirname, 'tsconfig.lib.json'),
|
||||
},
|
||||
}),
|
||||
wyw({
|
||||
include: [
|
||||
'**/OverflowingTextWithTooltip.tsx',
|
||||
'**/Chip.tsx',
|
||||
'**/Tag.tsx',
|
||||
],
|
||||
babelOptions: {
|
||||
presets: ['@babel/preset-typescript', '@babel/preset-react'],
|
||||
},
|
||||
}),
|
||||
],
|
||||
|
||||
// Configuration for building your library.
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user