mirror of
				https://github.com/lingble/twenty.git
				synced 2025-10-30 12:22:29 +00: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:
		| @@ -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}> | ||||
|           <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) | ||||
|           : '' | ||||
|   if (!isDefined(fieldValue)) { | ||||
|     return <></>; | ||||
|   } | ||||
|       maxWidth={maxWidth} | ||||
|     /> | ||||
|   ); | ||||
|  | ||||
|   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} /> | ||||
|           )} | ||||
|       {isTrue ? <IconCheck size={iconSizeSm} /> : <IconX size={iconSizeSm} />} | ||||
|       <StyledBooleanFieldValue> | ||||
|             {value ? 'True' : 'False'} | ||||
|         {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) => ( | ||||
| export const EmailDisplay = ({ value }: EmailDisplayProps) => { | ||||
|   if (!isDefined(value)) { | ||||
|     return <ContactLink href="#">{value}</ContactLink>; | ||||
|   } | ||||
|  | ||||
|   if (!validateEmail(value)) { | ||||
|     return <ContactLink href="#">{value}</ContactLink>; | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <EllipsisDisplay> | ||||
|     {value && validateEmail(value) ? ( | ||||
|       <ContactLink | ||||
|         href={`mailto:${value}`} | ||||
|         onClick={(event: MouseEvent<HTMLElement>) => { | ||||
| @@ -24,8 +42,6 @@ export const EmailDisplay = ({ value }: EmailDisplayProps) => ( | ||||
|       > | ||||
|         {value} | ||||
|       </ContactLink> | ||||
|     ) : ( | ||||
|       <ContactLink href="#">{value}</ContactLink> | ||||
|     )} | ||||
|     </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,204 +1,21 @@ | ||||
| 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> = [ | ||||
|   { | ||||
|     __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/', | ||||
|     }, | ||||
|     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', | ||||
|       }, | ||||
|       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: '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], | ||||
| export const getCompanyDuplicateMock = () => { | ||||
|   return { | ||||
|     ...companiesQueryResult.companies.edges[0].node, | ||||
|     id: '8b40856a-2ec9-4c03-8bc0-c032c89e1824', | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| export const mockedEmptyCompanyData = { | ||||
| export const getEmptyCompanyMock = () => { | ||||
|   return { | ||||
|     id: '9231e6ee-4cc2-4c7b-8c55-dff16f4d968a', | ||||
|     name: '', | ||||
|     domainName: '', | ||||
|     address: '', | ||||
|     accountOwner: null, | ||||
|   annualRecurringRevenue: null, | ||||
|     createdAt: null, | ||||
|     updatedAt: null, | ||||
|     employees: null, | ||||
| @@ -207,4 +24,370 @@ export const mockedEmptyCompanyData = { | ||||
|     xLink: null, | ||||
|     _activityCount: null, | ||||
|     __typename: 'Company', | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| export const companiesQueryResult = { | ||||
|   companies: { | ||||
|     __typename: 'CompanyConnection', | ||||
|     totalCount: 13, | ||||
|     pageInfo: { | ||||
|       __typename: 'PageInfo', | ||||
|       hasNextPage: false, | ||||
|       startCursor: | ||||
|         'WzEsICIyMDIwMjAyMC0zZWMzLTRmZTMtODk5Ny1iNzZhYTBiZmE0MDgiXQ==', | ||||
|       endCursor: 'WzEzLCAiMjAyMDIwMjAtMTQ1NS00YzU3LWFmYWYtZGQ1ZGMwODYzNjFkIl0=', | ||||
|     }, | ||||
|     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: '', | ||||
|             }, | ||||
|           }, | ||||
|         }, | ||||
|       }, | ||||
|       { | ||||
|         __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: '', | ||||
|           }, | ||||
|         }, | ||||
|       }, | ||||
|     ], | ||||
|   }, | ||||
| }; | ||||
|   | ||||
| @@ -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}> | ||||
|           <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> | ||||
|       {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> | ||||
|       )} | ||||
|       {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
		Reference in New Issue
	
	Block a user
	 Lucas Bordeau
					Lucas Bordeau