From 92aa0bd888310503da44d3e9377fc79f46735ff4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tha=C3=AFs?= Date: Fri, 8 Mar 2024 06:22:23 -0300 Subject: [PATCH 1/6] feat: add Month headers to Show Page Calendar tab (#4326) Closes #4288 --- package.json | 2 ++ .../calendar/components/Calendar.tsx | 31 ++++++++++++++++--- .../display/typography/components/H3Title.tsx | 19 ++++++++++++ .../__stories__/H3Title.stories.tsx | 22 +++++++++++++ yarn.lock | 11 +++++++ 5 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 packages/twenty-front/src/modules/ui/display/typography/components/H3Title.tsx create mode 100644 packages/twenty-front/src/modules/ui/display/typography/components/__stories__/H3Title.stories.tsx diff --git a/package.json b/package.json index 704922c45dd1..cc9e84de47e5 100644 --- a/package.json +++ b/package.json @@ -111,6 +111,7 @@ "lodash.isequal": "^4.5.0", "lodash.isobject": "^3.0.2", "lodash.kebabcase": "^4.1.1", + "lodash.mapvalues": "^4.6.0", "lodash.merge": "^4.6.2", "lodash.omit": "^4.5.0", "lodash.pick": "^4.4.0", @@ -225,6 +226,7 @@ "@types/lodash.isequal": "^4.5.7", "@types/lodash.isobject": "^3.0.7", "@types/lodash.kebabcase": "^4.1.7", + "@types/lodash.mapvalues": "^4.6.9", "@types/lodash.snakecase": "^4.1.7", "@types/lodash.upperfirst": "^4.3.7", "@types/luxon": "^3.3.0", diff --git a/packages/twenty-front/src/modules/activities/calendar/components/Calendar.tsx b/packages/twenty-front/src/modules/activities/calendar/components/Calendar.tsx index 85d9c603d7a4..97ad87b4e9d6 100644 --- a/packages/twenty-front/src/modules/activities/calendar/components/Calendar.tsx +++ b/packages/twenty-front/src/modules/activities/calendar/components/Calendar.tsx @@ -1,8 +1,11 @@ import styled from '@emotion/styled'; -import { startOfMonth } from 'date-fns'; +import { format, getYear, startOfMonth } from 'date-fns'; +import mapValues from 'lodash.mapvalues'; import { CalendarMonthCard } from '@/activities/calendar/components/CalendarMonthCard'; import { sortCalendarEventsDesc } from '@/activities/calendar/utils/sortCalendarEvents'; +import { H3Title } from '@/ui/display/typography/components/H3Title'; +import { Section } from '@/ui/layout/section/components/Section'; import { mockedCalendarEvents } from '~/testing/mock-data/calendar'; import { groupArrayItemsBy } from '~/utils/array/groupArrayItemsBy'; import { sortDesc } from '~/utils/sort'; @@ -16,6 +19,10 @@ const StyledContainer = styled.div` width: 100%; `; +const StyledYear = styled.span` + color: ${({ theme }) => theme.font.color.light}; +`; + export const Calendar = () => { const sortedCalendarEvents = [...mockedCalendarEvents].sort( sortCalendarEventsDesc, @@ -27,18 +34,32 @@ export const Calendar = () => { const sortedMonthTimes = Object.keys(calendarEventsByMonthTime) .map(Number) .sort(sortDesc); + const monthTimesByYear = groupArrayItemsBy(sortedMonthTimes, getYear); + const lastMonthTimeByYear = mapValues(monthTimesByYear, (monthTimes = []) => + Math.max(...monthTimes), + ); return ( {sortedMonthTimes.map((monthTime) => { const monthCalendarEvents = calendarEventsByMonthTime[monthTime]; + const year = getYear(monthTime); + const isLastMonthOfYear = lastMonthTimeByYear[year] === monthTime; + const monthLabel = format(monthTime, 'MMMM'); return ( !!monthCalendarEvents?.length && ( - +
+ + {monthLabel} + {isLastMonthOfYear && {year}} + + } + /> + +
) ); })} diff --git a/packages/twenty-front/src/modules/ui/display/typography/components/H3Title.tsx b/packages/twenty-front/src/modules/ui/display/typography/components/H3Title.tsx new file mode 100644 index 000000000000..4e8f8dcaae16 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/display/typography/components/H3Title.tsx @@ -0,0 +1,19 @@ +import { ReactNode } from 'react'; +import styled from '@emotion/styled'; + +type H3TitleProps = { + title: ReactNode; + className?: string; +}; + +const StyledH3Title = styled.h3` + color: ${({ theme }) => theme.font.color.primary}; + font-size: ${({ theme }) => theme.font.size.lg}; + font-weight: ${({ theme }) => theme.font.weight.semiBold}; + margin: 0; + margin-bottom: ${({ theme }) => theme.spacing(4)}; +`; + +export const H3Title = ({ title, className }: H3TitleProps) => { + return {title}; +}; diff --git a/packages/twenty-front/src/modules/ui/display/typography/components/__stories__/H3Title.stories.tsx b/packages/twenty-front/src/modules/ui/display/typography/components/__stories__/H3Title.stories.tsx new file mode 100644 index 000000000000..7ea720df9e08 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/display/typography/components/__stories__/H3Title.stories.tsx @@ -0,0 +1,22 @@ +import { Meta, StoryObj } from '@storybook/react'; + +import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator'; + +import { H3Title } from '../H3Title'; + +const meta: Meta = { + title: 'UI/Display/Typography/Title/H3Title', + component: H3Title, + decorators: [ComponentDecorator], + args: { + title: 'H3 title', + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + decorators: [ComponentDecorator], +}; diff --git a/yarn.lock b/yarn.lock index aa10f5b47ff9..7f3172c81aa5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15677,6 +15677,15 @@ __metadata: languageName: node linkType: hard +"@types/lodash.mapvalues@npm:^4.6.9": + version: 4.6.9 + resolution: "@types/lodash.mapvalues@npm:4.6.9" + dependencies: + "@types/lodash": "npm:*" + checksum: 09216965c2c64854c8c765f38f436333e931b98638a0b9d996bda7b6d538593a4cfe53cad7415d50593be48d76798763b97dc2d7925ab200dce1fa5b630ed3b4 + languageName: node + linkType: hard + "@types/lodash.merge@npm:^4.6.7": version: 4.6.9 resolution: "@types/lodash.merge@npm:4.6.9" @@ -44576,6 +44585,7 @@ __metadata: "@types/lodash.isequal": "npm:^4.5.7" "@types/lodash.isobject": "npm:^3.0.7" "@types/lodash.kebabcase": "npm:^4.1.7" + "@types/lodash.mapvalues": "npm:^4.6.9" "@types/lodash.merge": "npm:^4.6.7" "@types/lodash.pick": "npm:^4.3.7" "@types/lodash.snakecase": "npm:^4.1.7" @@ -44673,6 +44683,7 @@ __metadata: lodash.isequal: "npm:^4.5.0" lodash.isobject: "npm:^3.0.2" lodash.kebabcase: "npm:^4.1.1" + lodash.mapvalues: "npm:^4.6.0" lodash.merge: "npm:^4.6.2" lodash.omit: "npm:^4.5.0" lodash.pick: "npm:^4.4.0" From d2e2e50d8ad3d8d2667905927fefe8cc3b362005 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Fri, 8 Mar 2024 11:49:42 +0100 Subject: [PATCH 2/6] Fix consistency issuesin relation onDelete behavior while creating a new relation (#4372) * Fix consistency issuesin relation onDelete behavior while creating a new relation * Fix according to review --- .../layout/show-page/components/ShowPageRightContainer.tsx | 7 ------- .../metadata/object-metadata/object-metadata.service.ts | 1 + .../relation-metadata/relation-metadata.service.ts | 2 ++ .../utils/convert-on-delete-action-to-on-delete.util.ts | 2 +- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageRightContainer.tsx b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageRightContainer.tsx index 2dc6846bac9b..f8d4b9deefcd 100644 --- a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageRightContainer.tsx +++ b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageRightContainer.tsx @@ -9,7 +9,6 @@ import { ObjectTasks } from '@/activities/tasks/components/ObjectTasks'; import { Timeline } from '@/activities/timeline/components/Timeline'; import { TimelineQueryEffect } from '@/activities/timeline/components/TimelineQueryEffect'; import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity'; -import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { IconCalendarEvent, @@ -65,11 +64,6 @@ export const ShowPageRightContainer = ({ const { getActiveTabIdState } = useTabList(TAB_LIST_COMPONENT_ID); const activeTabId = useRecoilValue(getActiveTabIdState()); - const { objectMetadataItem: targetableObjectMetadataItem } = - useObjectMetadataItem({ - objectNameSingular: targetableObject.targetObjectNameSingular, - }); - const shouldDisplayCalendarTab = useIsFeatureEnabled('IS_CALENDAR_ENABLED'); const shouldDisplayEmailsTab = (emails && @@ -101,7 +95,6 @@ export const ShowPageRightContainer = ({ title: 'Files', Icon: IconPaperclip, hide: !notes, - disabled: targetableObjectMetadataItem.isCustom, }, { id: 'emails', diff --git a/packages/twenty-server/src/metadata/object-metadata/object-metadata.service.ts b/packages/twenty-server/src/metadata/object-metadata/object-metadata.service.ts index 191527ad54f4..85828e8db779 100644 --- a/packages/twenty-server/src/metadata/object-metadata/object-metadata.service.ts +++ b/packages/twenty-server/src/metadata/object-metadata/object-metadata.service.ts @@ -397,6 +397,7 @@ export class ObjectMetadataService extends TypeOrmQueryService { if (!onDeleteAction) { - return; + return 'SET NULL'; } switch (onDeleteAction) { From 250bb6134e01ae829f584e72ba994d5ccc467bd2 Mon Sep 17 00:00:00 2001 From: Weiko Date: Fri, 8 Mar 2024 14:06:21 +0100 Subject: [PATCH 3/6] [messaging] remove partial sync retry and fix missing datasource error (#4371) * [messaging] remove partial sync retry and fix missing datasource error * revert * fix * add 429 * fix * fix * fix * remove duplicate log * fix cron pattern --- .../integrations/message-queue/jobs.module.ts | 2 ++ .../fetch-all-workspaces-messages.job.ts | 20 ++++++++++++++----- .../commands/gmail-partial-sync.command.ts | 3 --- .../fetch-messages-by-batches.service.ts | 2 -- .../services/gmail-partial-sync.service.ts | 20 +++++++++++++++++-- 5 files changed, 35 insertions(+), 12 deletions(-) diff --git a/packages/twenty-server/src/integrations/message-queue/jobs.module.ts b/packages/twenty-server/src/integrations/message-queue/jobs.module.ts index d38b47fab4a2..c3d2442c521a 100644 --- a/packages/twenty-server/src/integrations/message-queue/jobs.module.ts +++ b/packages/twenty-server/src/integrations/message-queue/jobs.module.ts @@ -33,6 +33,7 @@ import { UserWorkspaceModule } from 'src/core/user-workspace/user-workspace.modu import { StripeModule } from 'src/core/billing/stripe/stripe.module'; import { Workspace } from 'src/core/workspace/workspace.entity'; import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity'; +import { DataSourceEntity } from 'src/metadata/data-source/data-source.entity'; @Module({ imports: [ @@ -51,6 +52,7 @@ import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity'; ThreadCleanerModule, TypeORMModule, TypeOrmModule.forFeature([Workspace, FeatureFlagEntity], 'core'), + TypeOrmModule.forFeature([DataSourceEntity], 'metadata'), UserModule, UserWorkspaceModule, WorkspaceDataSourceModule, diff --git a/packages/twenty-server/src/workspace/messaging/commands/crons/fetch-all-workspaces-messages.job.ts b/packages/twenty-server/src/workspace/messaging/commands/crons/fetch-all-workspaces-messages.job.ts index 9f5712d3ad12..9d02f2705f4c 100644 --- a/packages/twenty-server/src/workspace/messaging/commands/crons/fetch-all-workspaces-messages.job.ts +++ b/packages/twenty-server/src/workspace/messaging/commands/crons/fetch-all-workspaces-messages.job.ts @@ -1,7 +1,7 @@ import { Inject, Injectable, Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; +import { Repository, In } from 'typeorm'; import { MessageQueueJob } from 'src/integrations/message-queue/interfaces/message-queue-job.interface'; @@ -13,6 +13,7 @@ import { GmailPartialSyncJobData, GmailPartialSyncJob, } from 'src/workspace/messaging/jobs/gmail-partial-sync.job'; +import { DataSourceEntity } from 'src/metadata/data-source/data-source.entity'; @Injectable() export class FetchAllWorkspacesMessagesJob @@ -23,6 +24,8 @@ export class FetchAllWorkspacesMessagesJob constructor( @InjectRepository(Workspace, 'core') private readonly workspaceRepository: Repository, + @InjectRepository(DataSourceEntity, 'metadata') + private readonly dataSourceRepository: Repository, @Inject(MessageQueue.messagingQueue) private readonly messageQueueService: MessageQueueService, private readonly connectedAccountService: ConnectedAccountService, @@ -38,7 +41,17 @@ export class FetchAllWorkspacesMessagesJob }) ).map((workspace) => workspace.id); - for (const workspaceId of workspaceIds) { + const dataSources = await this.dataSourceRepository.find({ + where: { + workspaceId: In(workspaceIds), + }, + }); + + const workspaceIdsWithDataSources = new Set( + dataSources.map((dataSource) => dataSource.workspaceId), + ); + + for (const workspaceId of workspaceIdsWithDataSources) { await this.fetchWorkspaceMessages(workspaceId); } } @@ -55,9 +68,6 @@ export class FetchAllWorkspacesMessagesJob workspaceId, connectedAccountId: connectedAccount.id, }, - { - retryLimit: 2, - }, ); } } catch (error) { diff --git a/packages/twenty-server/src/workspace/messaging/commands/gmail-partial-sync.command.ts b/packages/twenty-server/src/workspace/messaging/commands/gmail-partial-sync.command.ts index b911f6122810..1340236304d8 100644 --- a/packages/twenty-server/src/workspace/messaging/commands/gmail-partial-sync.command.ts +++ b/packages/twenty-server/src/workspace/messaging/commands/gmail-partial-sync.command.ts @@ -56,9 +56,6 @@ export class GmailPartialSyncCommand extends CommandRunner { workspaceId, connectedAccountId: connectedAccount.id, }, - { - retryLimit: 2, - }, ); } } diff --git a/packages/twenty-server/src/workspace/messaging/services/fetch-messages-by-batches.service.ts b/packages/twenty-server/src/workspace/messaging/services/fetch-messages-by-batches.service.ts index bde65dc2a15d..a88ef5db67f5 100644 --- a/packages/twenty-server/src/workspace/messaging/services/fetch-messages-by-batches.service.ts +++ b/packages/twenty-server/src/workspace/messaging/services/fetch-messages-by-batches.service.ts @@ -187,8 +187,6 @@ export class FetchMessagesByBatchesService { const formattedResponse = Promise.all( parsedResponses.map(async (message: GmailMessageParsedResponse) => { if (message.error) { - console.log('Error', message.error); - errors.push(message.error); return; diff --git a/packages/twenty-server/src/workspace/messaging/services/gmail-partial-sync.service.ts b/packages/twenty-server/src/workspace/messaging/services/gmail-partial-sync.service.ts index fae82f7afbdd..7ce6419fdf3e 100644 --- a/packages/twenty-server/src/workspace/messaging/services/gmail-partial-sync.service.ts +++ b/packages/twenty-server/src/workspace/messaging/services/gmail-partial-sync.service.ts @@ -213,9 +213,25 @@ export class GmailPartialSyncService { } if (errors.length) { - throw new Error( - `Error fetching messages for ${connectedAccountId} in workspace ${workspaceId} during partial-sync`, + this.logger.error( + `Error fetching messages for ${connectedAccountId} in workspace ${workspaceId} during partial-sync: ${JSON.stringify( + errors, + null, + 2, + )}`, ); + const errorsCanBeIgnored = errors.every((error) => error.code === 404); + const errorsShouldBeRetried = errors.some((error) => error.code === 429); + + if (errorsShouldBeRetried) { + return; + } + + if (!errorsCanBeIgnored) { + throw new Error( + `Error fetching messages for ${connectedAccountId} in workspace ${workspaceId} during partial-sync`, + ); + } } startTime = Date.now(); From 0c17decfb992cfd2dc696504bfc6b7b9abb03925 Mon Sep 17 00:00:00 2001 From: bosiraphael <71827178+bosiraphael@users.noreply.github.com> Date: Fri, 8 Mar 2024 16:29:40 +0100 Subject: [PATCH 4/6] 4284 create calendarchanneleventassociation data model (#4350) * create model * add calendar channel relation * add calendar event relation * add to index.ts * done * updates * update relation * update relation * updates after comments --- ...annel-event-association.object-metadata.ts | 47 +++++++++++++++++++ .../calendar-channel.object-metadata.ts | 19 ++++++++ .../calendar-event.object-metadata.ts | 18 +++++++ .../standard-objects/index.ts | 2 + .../person.object-metadata.ts | 1 - 5 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/calendar-channel-event-association.object-metadata.ts diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/calendar-channel-event-association.object-metadata.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/calendar-channel-event-association.object-metadata.ts new file mode 100644 index 000000000000..d22382d987e3 --- /dev/null +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/calendar-channel-event-association.object-metadata.ts @@ -0,0 +1,47 @@ +import { FeatureFlagKeys } from 'src/core/feature-flag/feature-flag.entity'; +import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; +import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator'; +import { Gate } from 'src/workspace/workspace-sync-metadata/decorators/gate.decorator'; +import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator'; +import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator'; +import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; +import { CalendarEventObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/calendar-event.object-metadata'; + +@ObjectMetadata({ + namePlural: 'calendarChannelEventAssociations', + labelSingular: 'Calendar Channel Event Association', + labelPlural: 'Calendar Channel Event Associations', + description: 'Calendar Channel Event Associations', + icon: 'IconCalendar', +}) +@IsSystem() +@Gate({ + featureFlag: FeatureFlagKeys.IsCalendarEnabled, +}) +export class CalendarChannelEventAssociationObjectMetadata extends BaseObjectMetadata { + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Channel ID', + description: 'Channel ID', + icon: 'IconCalendar', + joinColumn: 'calendarChannelId', + }) + calendarChannel: CalendarEventObjectMetadata; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Event ID', + description: 'Event ID', + icon: 'IconCalendar', + joinColumn: 'calendarEventId', + }) + calendarEvent: CalendarEventObjectMetadata; + + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'Event external ID', + description: 'Event external ID', + icon: 'IconCalendar', + }) + eventExternalId: string; +} diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/calendar-channel.object-metadata.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/calendar-channel.object-metadata.ts index 267844cc57af..b30e06332c6c 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/calendar-channel.object-metadata.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/calendar-channel.object-metadata.ts @@ -1,3 +1,7 @@ +import { + RelationMetadataType, + RelationOnDeleteAction, +} from 'src/metadata/relation-metadata/relation-metadata.entity'; import { FeatureFlagKeys } from 'src/core/feature-flag/feature-flag.entity'; import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator'; @@ -6,6 +10,8 @@ import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-sy import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator'; import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; import { ConnectedAccountObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/connected-account.object-metadata'; +import { CalendarChannelEventAssociationObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/calendar-channel-event-association.object-metadata'; +import { RelationMetadata } from 'src/workspace/workspace-sync-metadata/decorators/relation-metadata.decorator'; export enum CalendarChannelVisibility { METADATA = 'METADATA', @@ -90,4 +96,17 @@ export class CalendarChannelObjectMetadata extends BaseObjectMetadata { icon: 'IconReload', }) syncCursor: string; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Calendar Channel Event Associations', + description: 'Calendar Channel Event Associations', + icon: 'IconCalendar', + }) + @RelationMetadata({ + type: RelationMetadataType.ONE_TO_MANY, + inverseSideTarget: () => CalendarChannelEventAssociationObjectMetadata, + onDelete: RelationOnDeleteAction.CASCADE, + }) + calendarChannelEventAssociations: CalendarChannelEventAssociationObjectMetadata[]; } diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/calendar-event.object-metadata.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/calendar-event.object-metadata.ts index 7aa7e7af5868..02857e619a1e 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/calendar-event.object-metadata.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/calendar-event.object-metadata.ts @@ -10,6 +10,7 @@ import { Gate } from 'src/workspace/workspace-sync-metadata/decorators/gate.deco import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator'; import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator'; import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; +import { CalendarChannelEventAssociationObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/calendar-channel-event-association.object-metadata'; import { CalendarEventAttendeeObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/calendar-event-attendee.object-metadata'; @ObjectMetadata({ @@ -128,6 +129,23 @@ export class CalendarEventObjectMetadata extends BaseObjectMetadata { }) recurringEventExternalId: string; + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Calendar Channel Event Associations', + description: 'Calendar Channel Event Associations', + icon: 'IconCalendar', + }) + @RelationMetadata({ + type: RelationMetadataType.ONE_TO_MANY, + inverseSideTarget: () => CalendarChannelEventAssociationObjectMetadata, + onDelete: RelationOnDeleteAction.CASCADE, + inverseSideFieldKey: 'calendarEvent', + }) + @Gate({ + featureFlag: 'IS_CALENDAR_ENABLED', + }) + calendarChannelEventAssociations: CalendarChannelEventAssociationObjectMetadata[]; + @FieldMetadata({ type: FieldMetadataType.RELATION, label: 'Event Attendees', diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/index.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/index.ts index 6c476298d476..988b1a22af66 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/index.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/index.ts @@ -24,6 +24,7 @@ import { ViewSortObjectMetadata } from 'src/workspace/workspace-sync-metadata/st import { ViewObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/view.object-metadata'; import { WebhookObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/webhook.object-metadata'; import { WorkspaceMemberObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/workspace-member.object-metadata'; +import { CalendarChannelEventAssociationObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/calendar-channel-event-association.object-metadata'; export const standardObjectMetadataDefinitions = [ ActivityTargetObjectMetadata, @@ -51,5 +52,6 @@ export const standardObjectMetadataDefinitions = [ MessageChannelMessageAssociationObjectMetadata, CalendarEventObjectMetadata, CalendarChannelObjectMetadata, + CalendarChannelEventAssociationObjectMetadata, CalendarEventAttendeeObjectMetadata, ]; diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/person.object-metadata.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/person.object-metadata.ts index 1b892b8978ee..09155cba437f 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/person.object-metadata.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/person.object-metadata.ts @@ -191,7 +191,6 @@ export class PersonObjectMetadata extends BaseObjectMetadata { @RelationMetadata({ type: RelationMetadataType.ONE_TO_MANY, inverseSideTarget: () => CalendarEventAttendeeObjectMetadata, - inverseSideFieldKey: 'person', }) @Gate({ featureFlag: 'IS_CALENDAR_ENABLED', From 40a3b7d849166d802eb997969f8fd8e45d131f48 Mon Sep 17 00:00:00 2001 From: Jeet Desai <52026385+jeet1desai@users.noreply.github.com> Date: Sat, 9 Mar 2024 02:00:45 +0530 Subject: [PATCH 5/6] Added CurrencyFieldInput design (#4254) * #4123 CurrencyFieldInput design is ready * resolved comment and currency code * resolved design comment --------- Co-authored-by: Lucas Bordeau --- .../input/components/CurrencyFieldInput.tsx | 15 +- .../utils/computeDraftValueFromFieldValue.ts | 3 +- .../field/input/components/CurrencyInput.tsx | 152 ++++++++++++++++++ .../CurrencyPickerDropdownButton.tsx | 108 +++++++++++++ .../CurrencyPickerDropdownSelect.tsx | 71 ++++++++ .../types/CurrencyPickerHotkeyScope.ts | 3 + .../components/MenuItemSelectAvatar.tsx | 2 +- 7 files changed, 348 insertions(+), 6 deletions(-) create mode 100644 packages/twenty-front/src/modules/ui/field/input/components/CurrencyInput.tsx create mode 100644 packages/twenty-front/src/modules/ui/input/components/internal/currency/components/CurrencyPickerDropdownButton.tsx create mode 100644 packages/twenty-front/src/modules/ui/input/components/internal/currency/components/CurrencyPickerDropdownSelect.tsx create mode 100644 packages/twenty-front/src/modules/ui/input/components/internal/currency/types/CurrencyPickerHotkeyScope.ts diff --git a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/CurrencyFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/CurrencyFieldInput.tsx index 5ab4617eddb8..eff716fb142e 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/CurrencyFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/meta-types/input/components/CurrencyFieldInput.tsx @@ -1,5 +1,5 @@ import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode'; -import { TextInput } from '@/ui/field/input/components/TextInput'; +import { CurrencyInput } from '@/ui/field/input/components/CurrencyInput'; import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay'; import { useCurrencyField } from '../../hooks/useCurrencyField'; @@ -79,10 +79,18 @@ export const CurrencyFieldInput = ({ }); }; + const handleSelect = (newValue: string) => { + setDraftValue({ + amount: draftValue?.amount ?? '', + currencyCode: newValue as CurrencyCode, + }); + }; + return ( - ); diff --git a/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts b/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts index ee11e822cbfa..72c024f7afe9 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts +++ b/packages/twenty-front/src/modules/object-record/record-field/utils/computeDraftValueFromFieldValue.ts @@ -1,4 +1,3 @@ -import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode'; import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition'; import { FieldInputDraftValue } from '@/object-record/record-field/types/FieldInputDraftValue'; import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'; @@ -31,7 +30,7 @@ export const computeDraftValueFromFieldValue = ({ return { amount: fieldValue?.amountMicros ? fieldValue.amountMicros / 1000000 : '', - currenyCode: CurrencyCode.USD, + currencyCode: fieldValue?.currencyCode ?? '', } as unknown as FieldInputDraftValue; } if (isFieldRelation(fieldDefinition)) { diff --git a/packages/twenty-front/src/modules/ui/field/input/components/CurrencyInput.tsx b/packages/twenty-front/src/modules/ui/field/input/components/CurrencyInput.tsx new file mode 100644 index 000000000000..8a72584b5a5c --- /dev/null +++ b/packages/twenty-front/src/modules/ui/field/input/components/CurrencyInput.tsx @@ -0,0 +1,152 @@ +import { ChangeEvent, useEffect, useMemo, useRef, useState } from 'react'; +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; + +import { useRegisterInputEvents } from '@/object-record/record-field/meta-types/input/hooks/useRegisterInputEvents'; +import { SETTINGS_FIELD_CURRENCY_CODES } from '@/settings/data-model/constants/SettingsFieldCurrencyCodes'; +import { IconComponent } from '@/ui/display/icon/types/IconComponent'; +import { CurrencyPickerDropdownButton } from '@/ui/input/components/internal/currency/components/CurrencyPickerDropdownButton'; +import { TEXT_INPUT_STYLE } from '@/ui/theme/constants/TextInputStyle'; + +export const StyledInput = styled.input` + margin: 0; + ${TEXT_INPUT_STYLE} + width: 100%; + padding: ${({ theme }) => `${theme.spacing(0)} ${theme.spacing(1)}`}; +`; + +const StyledContainer = styled.div` + align-items: center; + + border: none; + border-radius: ${({ theme }) => theme.border.radius.sm}; + box-shadow: ${({ theme }) => theme.boxShadow.strong}; + + display: flex; + justify-content: center; +`; + +const StyledIcon = styled.div` + align-items: center; + display: flex; + + & > svg { + padding-left: ${({ theme }) => theme.spacing(1)}; + color: ${({ theme }) => theme.font.color.tertiary}; + height: ${({ theme }) => theme.icon.size.md}px; + width: ${({ theme }) => theme.icon.size.md}px; + } +`; + +export type CurrencyInputProps = { + placeholder?: string; + autoFocus?: boolean; + value: string; + currencyCode: string; + onEnter: (newText: string) => void; + onEscape: (newText: string) => void; + onTab?: (newText: string) => void; + onShiftTab?: (newText: string) => void; + onClickOutside: (event: MouseEvent | TouchEvent, inputValue: string) => void; + onChange?: (newText: string) => void; + onSelect?: (newText: string) => void; + hotkeyScope: string; +}; + +type Currency = { + label: string; + value: string; + Icon: any; +}; + +export const CurrencyInput = ({ + autoFocus, + value, + currencyCode, + placeholder, + onEnter, + onEscape, + onTab, + onShiftTab, + onClickOutside, + onChange, + onSelect, + hotkeyScope, +}: CurrencyInputProps) => { + const theme = useTheme(); + + const [internalText, setInternalText] = useState(value); + const [internalCurrency, setInternalCurrency] = useState( + null, + ); + + const wrapperRef = useRef(null); + + const handleChange = (event: ChangeEvent) => { + setInternalText(event.target.value); + onChange?.(event.target.value); + }; + + const handleCurrencyChange = (currency: Currency) => { + setInternalCurrency(currency); + onSelect?.(currency.value); + }; + + useRegisterInputEvents({ + inputRef: wrapperRef, + inputValue: internalText, + onEnter, + onEscape, + onClickOutside, + onTab, + onShiftTab, + hotkeyScope, + }); + + const currencies = useMemo( + () => + Object.entries(SETTINGS_FIELD_CURRENCY_CODES).map( + ([key, { Icon, label }]) => ({ + value: key, + Icon, + label, + }), + ), + [], + ); + + useEffect(() => { + const currency = currencies.find(({ value }) => value === currencyCode); + if (currency) { + setInternalCurrency(currency); + } + }, [currencies, currencyCode]); + + useEffect(() => { + setInternalText(value); + }, [value]); + + const Icon: IconComponent = internalCurrency?.Icon; + + return ( + + + + {Icon && ( + + )} + + + + ); +}; diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/currency/components/CurrencyPickerDropdownButton.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/currency/components/CurrencyPickerDropdownButton.tsx new file mode 100644 index 000000000000..305ca9851da7 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/input/components/internal/currency/components/CurrencyPickerDropdownButton.tsx @@ -0,0 +1,108 @@ +import { useEffect, useState } from 'react'; +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; + +import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode'; +import { IconChevronDown } from '@/ui/display/icon'; +import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; +import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; + +import { CurrencyPickerHotkeyScope } from '../types/CurrencyPickerHotkeyScope'; + +import { CurrencyPickerDropdownSelect } from './CurrencyPickerDropdownSelect'; + +type StyledDropdownButtonProps = { + isUnfolded: boolean; +}; + +export const StyledDropdownButtonContainer = styled.div` + align-items: center; + color: ${({ color }) => color ?? 'none'}; + cursor: pointer; + display: flex; + border-right: ${({ theme }) => `1px solid ${theme.border.color.medium}`}; + height: 32px; + padding-left: ${({ theme }) => theme.spacing(2)}; + padding-right: ${({ theme }) => theme.spacing(2)}; + user-select: none; + &:hover { + filter: brightness(0.95); + } +`; + +const StyledIconContainer = styled.div` + align-items: center; + color: ${({ theme }) => theme.font.color.tertiary}; + display: flex; + gap: ${({ theme }) => theme.spacing(1)}; + font-weight: ${({ theme }) => theme.font.weight.medium}; + justify-content: center; + + svg { + align-items: center; + display: flex; + height: 16px; + justify-content: center; + } +`; + +export type Currency = { + label: string; + value: string; + Icon: any; +}; + +export const CurrencyPickerDropdownButton = ({ + valueCode, + onChange, + currencies, +}: { + valueCode: string; + onChange: (currency: Currency) => void; + currencies: Currency[]; +}) => { + const theme = useTheme(); + + const [selectedCurrency, setSelectedCurrency] = useState(); + + const { isDropdownOpen, closeDropdown } = useDropdown( + CurrencyPickerHotkeyScope.CurrencyPicker, + ); + + const handleChange = (currency: Currency) => { + onChange(currency); + closeDropdown(); + }; + + useEffect(() => { + const currency = currencies.find(({ value }) => value === valueCode); + if (currency) { + setSelectedCurrency(currency); + } + }, [valueCode, currencies]); + + return ( + + + {selectedCurrency ? selectedCurrency.value : CurrencyCode.USD} + + + + } + dropdownComponents={ + + } + dropdownPlacement="bottom-start" + dropdownOffset={{ x: 0, y: 4 }} + /> + ); +}; diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/currency/components/CurrencyPickerDropdownSelect.tsx b/packages/twenty-front/src/modules/ui/input/components/internal/currency/components/CurrencyPickerDropdownSelect.tsx new file mode 100644 index 000000000000..98f4348f0f97 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/input/components/internal/currency/components/CurrencyPickerDropdownSelect.tsx @@ -0,0 +1,71 @@ +import { useMemo, useState } from 'react'; + +import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; +import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; +import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput'; +import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; +import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; +import { MenuItemSelectAvatar } from '@/ui/navigation/menu-item/components/MenuItemSelectAvatar'; + +import { Currency } from './CurrencyPickerDropdownButton'; + +export const CurrencyPickerDropdownSelect = ({ + currencies, + selectedCurrency, + onChange, +}: { + currencies: Currency[]; + selectedCurrency?: Currency; + onChange: (currency: Currency) => void; +}) => { + const [searchFilter, setSearchFilter] = useState(''); + + const filteredCurrencies = useMemo( + () => + currencies.filter( + ({ value, label }) => + value + .toLocaleLowerCase() + .includes(searchFilter.toLocaleLowerCase()) || + label.toLocaleLowerCase().includes(searchFilter.toLocaleLowerCase()), + ), + [currencies, searchFilter], + ); + + return ( + + setSearchFilter(event.target.value)} + autoFocus + /> + + + {filteredCurrencies.length === 0 ? ( + + ) : ( + <> + {selectedCurrency && ( + onChange(selectedCurrency)} + text={`${selectedCurrency.label} (${selectedCurrency.value})`} + /> + )} + {filteredCurrencies.map((item) => + selectedCurrency?.value === item.value ? null : ( + onChange(item)} + text={`${item.label} (${item.value})`} + /> + ), + )} + + )} + + + ); +}; diff --git a/packages/twenty-front/src/modules/ui/input/components/internal/currency/types/CurrencyPickerHotkeyScope.ts b/packages/twenty-front/src/modules/ui/input/components/internal/currency/types/CurrencyPickerHotkeyScope.ts new file mode 100644 index 000000000000..cfeaebd04a65 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/input/components/internal/currency/types/CurrencyPickerHotkeyScope.ts @@ -0,0 +1,3 @@ +export enum CurrencyPickerHotkeyScope { + CurrencyPicker = 'currency-picker', +} diff --git a/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemSelectAvatar.tsx b/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemSelectAvatar.tsx index 59026208dead..14756606eb3e 100644 --- a/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemSelectAvatar.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/menu-item/components/MenuItemSelectAvatar.tsx @@ -12,7 +12,7 @@ import { import { StyledMenuItemSelect } from './MenuItemSelect'; type MenuItemSelectAvatarProps = { - avatar: ReactNode; + avatar?: ReactNode; selected: boolean; text: string; className?: string; From 40bea0d95e81e9fee09d6d8d1ba41e459b1d0770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tha=C3=AFs?= Date: Fri, 8 Mar 2024 17:55:30 -0300 Subject: [PATCH 6/6] feat: add Settings Object Edit identifiers form (#4300) * feat: add Settings Object Edit identifiers form Closes #3836 * fix: fix wrong imports after renaming directories --- .../LabelIdentifierFieldMetadataTypes.ts | 6 ++ .../utils/getActiveFieldMetadataItems.ts | 9 ++ .../utils/getDisabledFieldMetadataItems.ts | 9 ++ .../components/SettingsDataModelCardTitle.tsx | 11 ++ ...SettingsDataModelObjectIdentifiersForm.tsx | 101 ++++++++++++++++++ ...ettingsDataModelObjectSettingsFormCard.tsx | 47 ++++++-- .../src/modules/ui/display/icon/index.ts | 1 + .../modules/ui/input/components/Select.tsx | 30 ++++-- .../pages/object-record/RecordShowPage.tsx | 2 +- .../data-model/SettingsObjectDetail.tsx | 14 +-- .../data-model/SettingsObjectEdit.tsx | 6 +- 11 files changed, 212 insertions(+), 24 deletions(-) create mode 100644 packages/twenty-front/src/modules/object-metadata/constants/LabelIdentifierFieldMetadataTypes.ts create mode 100644 packages/twenty-front/src/modules/object-metadata/utils/getActiveFieldMetadataItems.ts create mode 100644 packages/twenty-front/src/modules/object-metadata/utils/getDisabledFieldMetadataItems.ts create mode 100644 packages/twenty-front/src/modules/settings/data-model/components/SettingsDataModelCardTitle.tsx create mode 100644 packages/twenty-front/src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectIdentifiersForm.tsx diff --git a/packages/twenty-front/src/modules/object-metadata/constants/LabelIdentifierFieldMetadataTypes.ts b/packages/twenty-front/src/modules/object-metadata/constants/LabelIdentifierFieldMetadataTypes.ts new file mode 100644 index 000000000000..fe25319198b2 --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/constants/LabelIdentifierFieldMetadataTypes.ts @@ -0,0 +1,6 @@ +import { FieldMetadataType } from '~/generated-metadata/graphql'; + +export const LABEL_IDENTIFIER_FIELD_METADATA_TYPES = [ + FieldMetadataType.Number, + FieldMetadataType.Text, +]; diff --git a/packages/twenty-front/src/modules/object-metadata/utils/getActiveFieldMetadataItems.ts b/packages/twenty-front/src/modules/object-metadata/utils/getActiveFieldMetadataItems.ts new file mode 100644 index 000000000000..e174c2b00489 --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/utils/getActiveFieldMetadataItems.ts @@ -0,0 +1,9 @@ +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; + +export const getActiveFieldMetadataItems = ( + objectMetadataItem: Pick, +) => + objectMetadataItem.fields.filter( + (fieldMetadataItem) => + fieldMetadataItem.isActive && !fieldMetadataItem.isSystem, + ); diff --git a/packages/twenty-front/src/modules/object-metadata/utils/getDisabledFieldMetadataItems.ts b/packages/twenty-front/src/modules/object-metadata/utils/getDisabledFieldMetadataItems.ts new file mode 100644 index 000000000000..52f781ccc203 --- /dev/null +++ b/packages/twenty-front/src/modules/object-metadata/utils/getDisabledFieldMetadataItems.ts @@ -0,0 +1,9 @@ +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; + +export const getDisabledFieldMetadataItems = ( + objectMetadataItem: Pick, +) => + objectMetadataItem.fields.filter( + (fieldMetadataItem) => + !fieldMetadataItem.isActive && !fieldMetadataItem.isSystem, + ); diff --git a/packages/twenty-front/src/modules/settings/data-model/components/SettingsDataModelCardTitle.tsx b/packages/twenty-front/src/modules/settings/data-model/components/SettingsDataModelCardTitle.tsx new file mode 100644 index 000000000000..9fbc11715a5f --- /dev/null +++ b/packages/twenty-front/src/modules/settings/data-model/components/SettingsDataModelCardTitle.tsx @@ -0,0 +1,11 @@ +import styled from '@emotion/styled'; + +const StyledTitle = styled.h3` + color: ${({ theme }) => theme.font.color.extraLight}; + font-size: ${({ theme }) => theme.font.size.sm}; + font-weight: ${({ theme }) => theme.font.weight.medium}; + margin: 0; + margin-bottom: ${({ theme }) => theme.spacing(4)}; +`; + +export { StyledTitle as SettingsDataModelCardTitle }; diff --git a/packages/twenty-front/src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectIdentifiersForm.tsx b/packages/twenty-front/src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectIdentifiersForm.tsx new file mode 100644 index 000000000000..6762b52693cf --- /dev/null +++ b/packages/twenty-front/src/modules/settings/data-model/objects/forms/components/SettingsDataModelObjectIdentifiersForm.tsx @@ -0,0 +1,101 @@ +import { useMemo } from 'react'; +import { Controller, useFormContext } from 'react-hook-form'; +import styled from '@emotion/styled'; +import { z } from 'zod'; + +import { LABEL_IDENTIFIER_FIELD_METADATA_TYPES } from '@/object-metadata/constants/LabelIdentifierFieldMetadataTypes'; +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { getActiveFieldMetadataItems } from '@/object-metadata/utils/getActiveFieldMetadataItems'; +import { objectMetadataItemSchema } from '@/object-metadata/validation-schemas/objectMetadataItemSchema'; +import { IconCircleOff } from '@/ui/display/icon'; +import { useIcons } from '@/ui/display/icon/hooks/useIcons'; +import { Select, SelectOption } from '@/ui/input/components/Select'; + +export const settingsDataModelObjectIdentifiersFormSchema = + objectMetadataItemSchema.pick({ + labelIdentifierFieldMetadataId: true, + imageIdentifierFieldMetadataId: true, + }); + +export type SettingsDataModelObjectIdentifiersFormValues = z.infer< + typeof settingsDataModelObjectIdentifiersFormSchema +>; + +type SettingsDataModelObjectIdentifiersFormProps = { + objectMetadataItem: ObjectMetadataItem; +}; + +const StyledContainer = styled.div` + display: flex; + gap: ${({ theme }) => theme.spacing(4)}; +`; + +export const SettingsDataModelObjectIdentifiersForm = ({ + objectMetadataItem, +}: SettingsDataModelObjectIdentifiersFormProps) => { + const { control } = + useFormContext(); + const { getIcon } = useIcons(); + + const labelIdentifierFieldOptions = useMemo( + () => + getActiveFieldMetadataItems(objectMetadataItem) + .filter( + ({ id, type }) => + LABEL_IDENTIFIER_FIELD_METADATA_TYPES.includes(type) || + objectMetadataItem.labelIdentifierFieldMetadataId === id, + ) + .map>((fieldMetadataItem) => ({ + Icon: getIcon(fieldMetadataItem.icon), + label: fieldMetadataItem.label, + value: fieldMetadataItem.id, + })), + [getIcon, objectMetadataItem], + ); + const imageIdentifierFieldOptions: SelectOption[] = []; + + const emptyOption: SelectOption = { + Icon: IconCircleOff, + label: 'None', + value: null, + }; + + return ( + + {[ + { + label: 'Record label', + fieldName: 'labelIdentifierFieldMetadataId' as const, + options: labelIdentifierFieldOptions, + }, + { + label: 'Record image', + fieldName: 'imageIdentifierFieldMetadataId' as const, + options: imageIdentifierFieldOptions, + }, + ].map(({ fieldName, label, options }) => ( + { + return ( +