Merge commit 'cd3a32e55503dc1e6b9873d812dd401bf7d51045' into context-menu-vertical

This commit is contained in:
brendanlaschke
2023-08-14 22:00:49 +02:00
179 changed files with 1971 additions and 3230 deletions

View File

@ -55,8 +55,5 @@ Close and reopen your terminal to start using nvm or run the following to use it
### Install Twenty project ### Install Twenty project
You are ready to install Twenty project. Follow the [Yarn install guide](/developer/local-setup#yarn-install) instructions. You are ready to install Twenty project. Follow the [Yarn install guide](/developer/local-setup#yarn-install-recommended) instructions.
We don't recommend to use Docker on WSL as it adds an extra layer of complexity. We don't recommend to use Docker on WSL as it adds an extra layer of complexity.

View File

@ -83,7 +83,8 @@ CREATE DATABASE "test";
Create a `twenty` user with password `twenty`: Create a `twenty` user with password `twenty`:
```sql ```sql
CREATE USER twenty PASSWORD twenty CREATE USER twenty PASSWORD 'twenty';
ALTER USER twenty CREATEDB;
``` ```
</TabItem> </TabItem>
</Tabs> </Tabs>

View File

@ -12,8 +12,8 @@ The easiest way to quickly try the app is to signup on [app.twenty.com](https://
The signup is free. The signup is free.
<ThemedImage sources={{light: "../../img/light-sign-in.png", dark:"../../img/dark-sign-in.png"}} style={{width:'100%', maxWidth:'800px'}}/> <ThemedImage sources={{light: "/img/light-sign-in.png", dark:"/img/dark-sign-in.png"}} style={{width:'100%', maxWidth:'800px'}}/>
## Developer documentation ## Developer documentation
If you are looking to install the project locally, either to try it or to contribute, check out our [developer guide](../developer/local-setup). If you are looking to install the project locally, either to try it or to contribute, check out our [developer guide](/developer/local-setup).

View File

@ -21,6 +21,7 @@ module.exports = {
{ {
files: ['*.js', '*.jsx', '*.ts', '*.tsx'], files: ['*.js', '*.jsx', '*.ts', '*.tsx'],
rules: { rules: {
'no-control-regex': 0,
'simple-import-sort/imports': [ 'simple-import-sort/imports': [
'error', 'error',
{ {

View File

@ -117,8 +117,6 @@ export type ActivityTarget = {
__typename?: 'ActivityTarget'; __typename?: 'ActivityTarget';
activity: Activity; activity: Activity;
activityId: Scalars['String']; activityId: Scalars['String'];
commentableId?: Maybe<Scalars['String']>;
commentableType?: Maybe<CommentableType>;
company?: Maybe<Company>; company?: Maybe<Company>;
companyId?: Maybe<Scalars['String']>; companyId?: Maybe<Scalars['String']>;
createdAt: Scalars['DateTime']; createdAt: Scalars['DateTime'];
@ -129,8 +127,6 @@ export type ActivityTarget = {
}; };
export type ActivityTargetCreateManyActivityInput = { export type ActivityTargetCreateManyActivityInput = {
commentableId?: InputMaybe<Scalars['String']>;
commentableType?: InputMaybe<CommentableType>;
companyId?: InputMaybe<Scalars['String']>; companyId?: InputMaybe<Scalars['String']>;
createdAt?: InputMaybe<Scalars['DateTime']>; createdAt?: InputMaybe<Scalars['DateTime']>;
id?: InputMaybe<Scalars['String']>; id?: InputMaybe<Scalars['String']>;
@ -145,8 +141,6 @@ export type ActivityTargetCreateManyActivityInputEnvelope = {
export type ActivityTargetCreateManyCompanyInput = { export type ActivityTargetCreateManyCompanyInput = {
activityId: Scalars['String']; activityId: Scalars['String'];
commentableId?: InputMaybe<Scalars['String']>;
commentableType?: InputMaybe<CommentableType>;
createdAt?: InputMaybe<Scalars['DateTime']>; createdAt?: InputMaybe<Scalars['DateTime']>;
id?: InputMaybe<Scalars['String']>; id?: InputMaybe<Scalars['String']>;
personId?: InputMaybe<Scalars['String']>; personId?: InputMaybe<Scalars['String']>;
@ -160,8 +154,6 @@ export type ActivityTargetCreateManyCompanyInputEnvelope = {
export type ActivityTargetCreateManyPersonInput = { export type ActivityTargetCreateManyPersonInput = {
activityId: Scalars['String']; activityId: Scalars['String'];
commentableId?: InputMaybe<Scalars['String']>;
commentableType?: InputMaybe<CommentableType>;
companyId?: InputMaybe<Scalars['String']>; companyId?: InputMaybe<Scalars['String']>;
createdAt?: InputMaybe<Scalars['DateTime']>; createdAt?: InputMaybe<Scalars['DateTime']>;
id?: InputMaybe<Scalars['String']>; id?: InputMaybe<Scalars['String']>;
@ -175,8 +167,6 @@ export type ActivityTargetCreateManyPersonInputEnvelope = {
export type ActivityTargetCreateManyWorkspaceInput = { export type ActivityTargetCreateManyWorkspaceInput = {
activityId: Scalars['String']; activityId: Scalars['String'];
commentableId?: InputMaybe<Scalars['String']>;
commentableType?: InputMaybe<CommentableType>;
companyId?: InputMaybe<Scalars['String']>; companyId?: InputMaybe<Scalars['String']>;
createdAt?: InputMaybe<Scalars['DateTime']>; createdAt?: InputMaybe<Scalars['DateTime']>;
id?: InputMaybe<Scalars['String']>; id?: InputMaybe<Scalars['String']>;
@ -231,8 +221,6 @@ export type ActivityTargetCreateOrConnectWithoutWorkspaceInput = {
}; };
export type ActivityTargetCreateWithoutActivityInput = { export type ActivityTargetCreateWithoutActivityInput = {
commentableId?: InputMaybe<Scalars['String']>;
commentableType?: InputMaybe<CommentableType>;
company?: InputMaybe<CompanyCreateNestedOneWithoutActivityTargetInput>; company?: InputMaybe<CompanyCreateNestedOneWithoutActivityTargetInput>;
createdAt?: InputMaybe<Scalars['DateTime']>; createdAt?: InputMaybe<Scalars['DateTime']>;
id?: InputMaybe<Scalars['String']>; id?: InputMaybe<Scalars['String']>;
@ -242,8 +230,6 @@ export type ActivityTargetCreateWithoutActivityInput = {
export type ActivityTargetCreateWithoutCompanyInput = { export type ActivityTargetCreateWithoutCompanyInput = {
activity: ActivityCreateNestedOneWithoutActivityTargetsInput; activity: ActivityCreateNestedOneWithoutActivityTargetsInput;
commentableId?: InputMaybe<Scalars['String']>;
commentableType?: InputMaybe<CommentableType>;
createdAt?: InputMaybe<Scalars['DateTime']>; createdAt?: InputMaybe<Scalars['DateTime']>;
id?: InputMaybe<Scalars['String']>; id?: InputMaybe<Scalars['String']>;
person?: InputMaybe<PersonCreateNestedOneWithoutActivityTargetInput>; person?: InputMaybe<PersonCreateNestedOneWithoutActivityTargetInput>;
@ -252,8 +238,6 @@ export type ActivityTargetCreateWithoutCompanyInput = {
export type ActivityTargetCreateWithoutPersonInput = { export type ActivityTargetCreateWithoutPersonInput = {
activity: ActivityCreateNestedOneWithoutActivityTargetsInput; activity: ActivityCreateNestedOneWithoutActivityTargetsInput;
commentableId?: InputMaybe<Scalars['String']>;
commentableType?: InputMaybe<CommentableType>;
company?: InputMaybe<CompanyCreateNestedOneWithoutActivityTargetInput>; company?: InputMaybe<CompanyCreateNestedOneWithoutActivityTargetInput>;
createdAt?: InputMaybe<Scalars['DateTime']>; createdAt?: InputMaybe<Scalars['DateTime']>;
id?: InputMaybe<Scalars['String']>; id?: InputMaybe<Scalars['String']>;
@ -262,8 +246,6 @@ export type ActivityTargetCreateWithoutPersonInput = {
export type ActivityTargetCreateWithoutWorkspaceInput = { export type ActivityTargetCreateWithoutWorkspaceInput = {
activity: ActivityCreateNestedOneWithoutActivityTargetsInput; activity: ActivityCreateNestedOneWithoutActivityTargetsInput;
commentableId?: InputMaybe<Scalars['String']>;
commentableType?: InputMaybe<CommentableType>;
company?: InputMaybe<CompanyCreateNestedOneWithoutActivityTargetInput>; company?: InputMaybe<CompanyCreateNestedOneWithoutActivityTargetInput>;
createdAt?: InputMaybe<Scalars['DateTime']>; createdAt?: InputMaybe<Scalars['DateTime']>;
id?: InputMaybe<Scalars['String']>; id?: InputMaybe<Scalars['String']>;
@ -286,8 +268,6 @@ export type ActivityTargetScalarWhereInput = {
NOT?: InputMaybe<Array<ActivityTargetScalarWhereInput>>; NOT?: InputMaybe<Array<ActivityTargetScalarWhereInput>>;
OR?: InputMaybe<Array<ActivityTargetScalarWhereInput>>; OR?: InputMaybe<Array<ActivityTargetScalarWhereInput>>;
activityId?: InputMaybe<StringFilter>; activityId?: InputMaybe<StringFilter>;
commentableId?: InputMaybe<StringNullableFilter>;
commentableType?: InputMaybe<EnumCommentableTypeNullableFilter>;
companyId?: InputMaybe<StringNullableFilter>; companyId?: InputMaybe<StringNullableFilter>;
createdAt?: InputMaybe<DateTimeFilter>; createdAt?: InputMaybe<DateTimeFilter>;
id?: InputMaybe<StringFilter>; id?: InputMaybe<StringFilter>;
@ -345,8 +325,6 @@ export type ActivityTargetWhereInput = {
OR?: InputMaybe<Array<ActivityTargetWhereInput>>; OR?: InputMaybe<Array<ActivityTargetWhereInput>>;
activity?: InputMaybe<ActivityRelationFilter>; activity?: InputMaybe<ActivityRelationFilter>;
activityId?: InputMaybe<StringFilter>; activityId?: InputMaybe<StringFilter>;
commentableId?: InputMaybe<StringNullableFilter>;
commentableType?: InputMaybe<EnumCommentableTypeNullableFilter>;
company?: InputMaybe<CompanyRelationFilter>; company?: InputMaybe<CompanyRelationFilter>;
companyId?: InputMaybe<StringNullableFilter>; companyId?: InputMaybe<StringNullableFilter>;
createdAt?: InputMaybe<DateTimeFilter>; createdAt?: InputMaybe<DateTimeFilter>;
@ -632,11 +610,6 @@ export type CommentWhereUniqueInput = {
id?: InputMaybe<Scalars['String']>; id?: InputMaybe<Scalars['String']>;
}; };
export enum CommentableType {
Company = 'Company',
Person = 'Person'
}
export type Company = { export type Company = {
__typename?: 'Company'; __typename?: 'Company';
ActivityTarget?: Maybe<Array<ActivityTarget>>; ActivityTarget?: Maybe<Array<ActivityTarget>>;
@ -837,13 +810,6 @@ export type EnumColorSchemeFilter = {
notIn?: InputMaybe<Array<ColorScheme>>; notIn?: InputMaybe<Array<ColorScheme>>;
}; };
export type EnumCommentableTypeNullableFilter = {
equals?: InputMaybe<CommentableType>;
in?: InputMaybe<Array<CommentableType>>;
not?: InputMaybe<NestedEnumCommentableTypeNullableFilter>;
notIn?: InputMaybe<Array<CommentableType>>;
};
export type EnumPipelineProgressableTypeFilter = { export type EnumPipelineProgressableTypeFilter = {
equals?: InputMaybe<PipelineProgressableType>; equals?: InputMaybe<PipelineProgressableType>;
in?: InputMaybe<Array<PipelineProgressableType>>; in?: InputMaybe<Array<PipelineProgressableType>>;
@ -1286,13 +1252,6 @@ export type NestedEnumColorSchemeFilter = {
notIn?: InputMaybe<Array<ColorScheme>>; notIn?: InputMaybe<Array<ColorScheme>>;
}; };
export type NestedEnumCommentableTypeNullableFilter = {
equals?: InputMaybe<CommentableType>;
in?: InputMaybe<Array<CommentableType>>;
not?: InputMaybe<NestedEnumCommentableTypeNullableFilter>;
notIn?: InputMaybe<Array<CommentableType>>;
};
export type NestedEnumPipelineProgressableTypeFilter = { export type NestedEnumPipelineProgressableTypeFilter = {
equals?: InputMaybe<PipelineProgressableType>; equals?: InputMaybe<PipelineProgressableType>;
in?: InputMaybe<Array<PipelineProgressableType>>; in?: InputMaybe<Array<PipelineProgressableType>>;
@ -2694,7 +2653,9 @@ export type CreateActivityMutationVariables = Exact<{
}>; }>;
export type CreateActivityMutation = { __typename?: 'Mutation', createOneActivity: { __typename?: 'Activity', id: string, createdAt: string, updatedAt: string, authorId: string, type: ActivityType, activityTargets?: Array<{ __typename?: 'ActivityTarget', id: string, createdAt: string, updatedAt: string, activityId: string, commentableType?: CommentableType | null, commentableId?: string | null, companyId?: string | null, personId?: string | null }> | null, comments?: Array<{ __typename?: 'Comment', id: string, createdAt: string, updatedAt: string, body: string, author: { __typename?: 'User', id: string } }> | null } }; export type CreateActivityMutation = { __typename?: 'Mutation', createOneActivity: { __typename?: 'Activity', id: string, createdAt: string, updatedAt: string, authorId: string, type: ActivityType, activityTargets?: Array<{ __typename?: 'ActivityTarget', id: string, createdAt: string, updatedAt: string, activityId: string, companyId?: string | null, personId?: string | null }> | null, comments?: Array<{ __typename?: 'Comment', id: string, createdAt: string, updatedAt: string, body: string, author: { __typename?: 'User', id: string } }> | null } };
export type ActivityQueryFragmentFragment = { __typename?: 'Activity', id: string, createdAt: string, title?: string | null, body?: string | null, type: ActivityType, completedAt?: string | null, dueAt?: string | null, assignee?: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string, avatarUrl?: string | null } | null, author: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string }, comments?: Array<{ __typename?: 'Comment', id: string, body: string, createdAt: string, updatedAt: string, author: { __typename?: 'User', id: string, displayName: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null } }> | null, activityTargets?: Array<{ __typename?: 'ActivityTarget', id: string, companyId?: string | null, personId?: string | null, company?: { __typename?: 'Company', id: string, name: string, domainName: string } | null, person?: { __typename?: 'Person', id: string, displayName: string, avatarUrl?: string | null } | null }> | null };
export type GetActivitiesByTargetsQueryVariables = Exact<{ export type GetActivitiesByTargetsQueryVariables = Exact<{
activityTargetIds: Array<Scalars['String']> | Scalars['String']; activityTargetIds: Array<Scalars['String']> | Scalars['String'];
@ -2702,7 +2663,7 @@ export type GetActivitiesByTargetsQueryVariables = Exact<{
}>; }>;
export type GetActivitiesByTargetsQuery = { __typename?: 'Query', findManyActivities: Array<{ __typename?: 'Activity', id: string, createdAt: string, title?: string | null, body?: string | null, type: ActivityType, completedAt?: string | null, dueAt?: string | null, assignee?: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string, avatarUrl?: string | null } | null, author: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string }, comments?: Array<{ __typename?: 'Comment', id: string, body: string, createdAt: string, updatedAt: string, author: { __typename?: 'User', id: string, displayName: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null } }> | null, activityTargets?: Array<{ __typename?: 'ActivityTarget', id: string, commentableType?: CommentableType | null, commentableId?: string | null, companyId?: string | null, personId?: string | null }> | null }> }; export type GetActivitiesByTargetsQuery = { __typename?: 'Query', findManyActivities: Array<{ __typename?: 'Activity', id: string, createdAt: string, title?: string | null, body?: string | null, type: ActivityType, completedAt?: string | null, dueAt?: string | null, assignee?: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string, avatarUrl?: string | null } | null, author: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string }, comments?: Array<{ __typename?: 'Comment', id: string, body: string, createdAt: string, updatedAt: string, author: { __typename?: 'User', id: string, displayName: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null } }> | null, activityTargets?: Array<{ __typename?: 'ActivityTarget', id: string, companyId?: string | null, personId?: string | null, company?: { __typename?: 'Company', id: string, name: string, domainName: string } | null, person?: { __typename?: 'Person', id: string, displayName: string, avatarUrl?: string | null } | null }> | null }> };
export type GetActivitiesQueryVariables = Exact<{ export type GetActivitiesQueryVariables = Exact<{
where: ActivityWhereInput; where: ActivityWhereInput;
@ -2710,14 +2671,14 @@ export type GetActivitiesQueryVariables = Exact<{
}>; }>;
export type GetActivitiesQuery = { __typename?: 'Query', findManyActivities: Array<{ __typename?: 'Activity', id: string, createdAt: string, title?: string | null, body?: string | null, type: ActivityType, completedAt?: string | null, dueAt?: string | null, assignee?: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string, avatarUrl?: string | null } | null, author: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string }, comments?: Array<{ __typename?: 'Comment', id: string }> | null, activityTargets?: Array<{ __typename?: 'ActivityTarget', id: string, commentableType?: CommentableType | null, commentableId?: string | null, companyId?: string | null, personId?: string | null }> | null }> }; export type GetActivitiesQuery = { __typename?: 'Query', findManyActivities: Array<{ __typename?: 'Activity', id: string, createdAt: string, title?: string | null, body?: string | null, type: ActivityType, completedAt?: string | null, dueAt?: string | null, assignee?: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string, avatarUrl?: string | null } | null, author: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string }, comments?: Array<{ __typename?: 'Comment', id: string, body: string, createdAt: string, updatedAt: string, author: { __typename?: 'User', id: string, displayName: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null } }> | null, activityTargets?: Array<{ __typename?: 'ActivityTarget', id: string, companyId?: string | null, personId?: string | null, company?: { __typename?: 'Company', id: string, name: string, domainName: string } | null, person?: { __typename?: 'Person', id: string, displayName: string, avatarUrl?: string | null } | null }> | null }> };
export type GetActivityQueryVariables = Exact<{ export type GetActivityQueryVariables = Exact<{
activityId: Scalars['String']; activityId: Scalars['String'];
}>; }>;
export type GetActivityQuery = { __typename?: 'Query', findManyActivities: Array<{ __typename?: 'Activity', id: string, createdAt: string, body?: string | null, title?: string | null, type: ActivityType, completedAt?: string | null, dueAt?: string | null, assignee?: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string, avatarUrl?: string | null } | null, author: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string }, comments?: Array<{ __typename?: 'Comment', id: string, body: string, createdAt: string, updatedAt: string, author: { __typename?: 'User', id: string, displayName: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null } }> | null, activityTargets?: Array<{ __typename?: 'ActivityTarget', id: string, commentableType?: CommentableType | null, commentableId?: string | null, companyId?: string | null, personId?: string | null }> | null }> }; export type GetActivityQuery = { __typename?: 'Query', findManyActivities: Array<{ __typename?: 'Activity', id: string, createdAt: string, title?: string | null, body?: string | null, type: ActivityType, completedAt?: string | null, dueAt?: string | null, assignee?: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string, avatarUrl?: string | null } | null, author: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string }, comments?: Array<{ __typename?: 'Comment', id: string, body: string, createdAt: string, updatedAt: string, author: { __typename?: 'User', id: string, displayName: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null } }> | null, activityTargets?: Array<{ __typename?: 'ActivityTarget', id: string, companyId?: string | null, personId?: string | null, company?: { __typename?: 'Company', id: string, name: string, domainName: string } | null, person?: { __typename?: 'Person', id: string, displayName: string, avatarUrl?: string | null } | null }> | null }> };
export type AddActivityTargetsOnActivityMutationVariables = Exact<{ export type AddActivityTargetsOnActivityMutationVariables = Exact<{
activityId: Scalars['String']; activityId: Scalars['String'];
@ -2725,7 +2686,7 @@ export type AddActivityTargetsOnActivityMutationVariables = Exact<{
}>; }>;
export type AddActivityTargetsOnActivityMutation = { __typename?: 'Mutation', updateOneActivity: { __typename?: 'Activity', id: string, createdAt: string, updatedAt: string, activityTargets?: Array<{ __typename?: 'ActivityTarget', id: string, createdAt: string, updatedAt: string, commentableType?: CommentableType | null, commentableId?: string | null, companyId?: string | null, personId?: string | null }> | null } }; export type AddActivityTargetsOnActivityMutation = { __typename?: 'Mutation', updateOneActivity: { __typename?: 'Activity', id: string, createdAt: string, updatedAt: string, activityTargets?: Array<{ __typename?: 'ActivityTarget', id: string, createdAt: string, updatedAt: string, companyId?: string | null, personId?: string | null }> | null } };
export type RemoveActivityTargetsOnActivityMutationVariables = Exact<{ export type RemoveActivityTargetsOnActivityMutationVariables = Exact<{
activityId: Scalars['String']; activityId: Scalars['String'];
@ -2733,7 +2694,7 @@ export type RemoveActivityTargetsOnActivityMutationVariables = Exact<{
}>; }>;
export type RemoveActivityTargetsOnActivityMutation = { __typename?: 'Mutation', updateOneActivity: { __typename?: 'Activity', id: string, createdAt: string, updatedAt: string, activityTargets?: Array<{ __typename?: 'ActivityTarget', id: string, createdAt: string, updatedAt: string, commentableType?: CommentableType | null, commentableId?: string | null, companyId?: string | null, personId?: string | null }> | null } }; export type RemoveActivityTargetsOnActivityMutation = { __typename?: 'Mutation', updateOneActivity: { __typename?: 'Activity', id: string, createdAt: string, updatedAt: string, activityTargets?: Array<{ __typename?: 'ActivityTarget', id: string, createdAt: string, updatedAt: string, companyId?: string | null, personId?: string | null }> | null } };
export type DeleteActivityMutationVariables = Exact<{ export type DeleteActivityMutationVariables = Exact<{
activityId: Scalars['String']; activityId: Scalars['String'];
@ -2833,22 +2794,22 @@ export type GetCompanyQueryVariables = Exact<{
export type GetCompanyQuery = { __typename?: 'Query', findUniqueCompany: { __typename?: 'Company', id: string, domainName: string, name: string, createdAt: string, address: string, linkedinUrl?: string | null, employees?: number | null, _activityCount: number, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string, avatarUrl?: string | null } | null, Favorite?: Array<{ __typename?: 'Favorite', id: string, person?: { __typename?: 'Person', id: string } | null, company?: { __typename?: 'Company', id: string } | null }> | null } }; export type GetCompanyQuery = { __typename?: 'Query', findUniqueCompany: { __typename?: 'Company', id: string, domainName: string, name: string, createdAt: string, address: string, linkedinUrl?: string | null, employees?: number | null, _activityCount: number, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string, avatarUrl?: string | null } | null, Favorite?: Array<{ __typename?: 'Favorite', id: string, person?: { __typename?: 'Person', id: string } | null, company?: { __typename?: 'Company', id: string } | null }> | null } };
export type CompanyFieldsFragmentFragment = { __typename?: 'Company', address: string, createdAt: string, domainName: string, employees?: number | null, linkedinUrl?: string | null, id: string, name: string, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string, avatarUrl?: string | null } | null };
export type UpdateOneCompanyMutationVariables = Exact<{ export type UpdateOneCompanyMutationVariables = Exact<{
where: CompanyWhereUniqueInput; where: CompanyWhereUniqueInput;
data: CompanyUpdateInput; data: CompanyUpdateInput;
}>; }>;
export type UpdateOneCompanyMutation = { __typename?: 'Mutation', updateOneCompany?: { __typename?: 'Company', address: string, createdAt: string, domainName: string, employees?: number | null, linkedinUrl?: string | null, id: string, name: string, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null } | null } | null }; export type UpdateOneCompanyMutation = { __typename?: 'Mutation', updateOneCompany?: { __typename?: 'Company', address: string, createdAt: string, domainName: string, employees?: number | null, linkedinUrl?: string | null, id: string, name: string, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string, avatarUrl?: string | null } | null } | null };
export type InsertCompanyFragmentFragment = { __typename?: 'Company', domainName: string, address: string, id: string, name: string, createdAt: string };
export type InsertOneCompanyMutationVariables = Exact<{ export type InsertOneCompanyMutationVariables = Exact<{
data: CompanyCreateInput; data: CompanyCreateInput;
}>; }>;
export type InsertOneCompanyMutation = { __typename?: 'Mutation', createOneCompany: { __typename?: 'Company', domainName: string, address: string, id: string, name: string, createdAt: string } }; export type InsertOneCompanyMutation = { __typename?: 'Mutation', createOneCompany: { __typename?: 'Company', address: string, createdAt: string, domainName: string, employees?: number | null, linkedinUrl?: string | null, id: string, name: string, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string, avatarUrl?: string | null } | null } };
export type DeleteManyCompaniesMutationVariables = Exact<{ export type DeleteManyCompaniesMutationVariables = Exact<{
ids?: InputMaybe<Array<Scalars['String']> | Scalars['String']>; ids?: InputMaybe<Array<Scalars['String']> | Scalars['String']>;
@ -3073,7 +3034,7 @@ export type SearchCompanyQueryVariables = Exact<{
}>; }>;
export type SearchCompanyQuery = { __typename?: 'Query', searchResults: Array<{ __typename?: 'Company', id: string, name: string, domainName: string }> }; export type SearchCompanyQuery = { __typename?: 'Query', searchResults: Array<{ __typename?: 'Company', address: string, createdAt: string, domainName: string, employees?: number | null, linkedinUrl?: string | null, id: string, name: string, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string, avatarUrl?: string | null } | null }> };
export type SearchActivityQueryVariables = Exact<{ export type SearchActivityQueryVariables = Exact<{
where?: InputMaybe<ActivityWhereInput>; where?: InputMaybe<ActivityWhereInput>;
@ -3128,13 +3089,6 @@ export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }
export type DeleteUserAccountMutation = { __typename?: 'Mutation', deleteUserAccount: { __typename?: 'User', id: string } }; export type DeleteUserAccountMutation = { __typename?: 'Mutation', deleteUserAccount: { __typename?: 'User', id: string } };
export type CreateViewFieldMutationVariables = Exact<{
data: ViewFieldCreateInput;
}>;
export type CreateViewFieldMutation = { __typename?: 'Mutation', createOneViewField: { __typename?: 'ViewField', id: string, fieldName: string, isVisible: boolean, sizeInPx: number, index: number } };
export type CreateViewFieldsMutationVariables = Exact<{ export type CreateViewFieldsMutationVariables = Exact<{
data: Array<ViewFieldCreateManyInput> | ViewFieldCreateManyInput; data: Array<ViewFieldCreateManyInput> | ViewFieldCreateManyInput;
}>; }>;
@ -3230,6 +3184,58 @@ export type DeleteCurrentWorkspaceMutationVariables = Exact<{ [key: string]: nev
export type DeleteCurrentWorkspaceMutation = { __typename?: 'Mutation', deleteCurrentWorkspace: { __typename?: 'Workspace', id: string } }; export type DeleteCurrentWorkspaceMutation = { __typename?: 'Mutation', deleteCurrentWorkspace: { __typename?: 'Workspace', id: string } };
export const ActivityQueryFragmentFragmentDoc = gql`
fragment ActivityQueryFragment on Activity {
id
createdAt
title
body
type
completedAt
dueAt
assignee {
id
firstName
lastName
displayName
avatarUrl
}
author {
id
firstName
lastName
displayName
}
comments {
id
body
createdAt
updatedAt
author {
id
displayName
firstName
lastName
avatarUrl
}
}
activityTargets {
id
companyId
personId
company {
id
name
domainName
}
person {
id
displayName
avatarUrl
}
}
}
`;
export const ActivityUpdatePartsFragmentDoc = gql` export const ActivityUpdatePartsFragmentDoc = gql`
fragment ActivityUpdateParts on Activity { fragment ActivityUpdateParts on Activity {
id id
@ -3246,13 +3252,21 @@ export const ActivityUpdatePartsFragmentDoc = gql`
} }
} }
`; `;
export const InsertCompanyFragmentFragmentDoc = gql` export const CompanyFieldsFragmentFragmentDoc = gql`
fragment InsertCompanyFragment on Company { fragment CompanyFieldsFragment on Company {
domainName accountOwner {
id
email
displayName
avatarUrl
}
address address
createdAt
domainName
employees
linkedinUrl
id id
name name
createdAt
} }
`; `;
export const InsertPersonFragmentFragmentDoc = gql` export const InsertPersonFragmentFragmentDoc = gql`
@ -3326,8 +3340,6 @@ export const CreateActivityDocument = gql`
createdAt createdAt
updatedAt updatedAt
activityId activityId
commentableType
commentableId
companyId companyId
personId personId
} }
@ -3373,51 +3385,12 @@ export const GetActivitiesByTargetsDocument = gql`
query GetActivitiesByTargets($activityTargetIds: [String!]!, $orderBy: [ActivityOrderByWithRelationInput!]) { query GetActivitiesByTargets($activityTargetIds: [String!]!, $orderBy: [ActivityOrderByWithRelationInput!]) {
findManyActivities( findManyActivities(
orderBy: $orderBy orderBy: $orderBy
where: {activityTargets: {some: {commentableId: {in: $activityTargetIds}}}} where: {activityTargets: {some: {OR: [{personId: {in: $activityTargetIds}}, {companyId: {in: $activityTargetIds}}]}}}
) { ) {
id ...ActivityQueryFragment
createdAt
title
body
type
completedAt
dueAt
assignee {
id
firstName
lastName
displayName
avatarUrl
}
author {
id
firstName
lastName
displayName
}
comments {
id
body
createdAt
updatedAt
author {
id
displayName
firstName
lastName
avatarUrl
}
}
activityTargets {
id
commentableType
commentableId
companyId
personId
}
} }
} }
`; ${ActivityQueryFragmentFragmentDoc}`;
/** /**
* __useGetActivitiesByTargetsQuery__ * __useGetActivitiesByTargetsQuery__
@ -3450,39 +3423,10 @@ export type GetActivitiesByTargetsQueryResult = Apollo.QueryResult<GetActivities
export const GetActivitiesDocument = gql` export const GetActivitiesDocument = gql`
query GetActivities($where: ActivityWhereInput!, $orderBy: [ActivityOrderByWithRelationInput!]) { query GetActivities($where: ActivityWhereInput!, $orderBy: [ActivityOrderByWithRelationInput!]) {
findManyActivities(orderBy: $orderBy, where: $where) { findManyActivities(orderBy: $orderBy, where: $where) {
id ...ActivityQueryFragment
createdAt
title
body
type
completedAt
dueAt
assignee {
id
firstName
lastName
displayName
avatarUrl
}
author {
id
firstName
lastName
displayName
}
comments {
id
}
activityTargets {
id
commentableType
commentableId
companyId
personId
}
} }
} }
`; ${ActivityQueryFragmentFragmentDoc}`;
/** /**
* __useGetActivitiesQuery__ * __useGetActivitiesQuery__
@ -3515,49 +3459,10 @@ export type GetActivitiesQueryResult = Apollo.QueryResult<GetActivitiesQuery, Ge
export const GetActivityDocument = gql` export const GetActivityDocument = gql`
query GetActivity($activityId: String!) { query GetActivity($activityId: String!) {
findManyActivities(where: {id: {equals: $activityId}}) { findManyActivities(where: {id: {equals: $activityId}}) {
id ...ActivityQueryFragment
createdAt
body
title
type
completedAt
dueAt
assignee {
id
firstName
lastName
displayName
avatarUrl
}
author {
id
firstName
lastName
displayName
}
comments {
id
body
createdAt
updatedAt
author {
id
displayName
firstName
lastName
avatarUrl
}
}
activityTargets {
id
commentableType
commentableId
companyId
personId
}
} }
} }
`; ${ActivityQueryFragmentFragmentDoc}`;
/** /**
* __useGetActivityQuery__ * __useGetActivityQuery__
@ -3599,8 +3504,6 @@ export const AddActivityTargetsOnActivityDocument = gql`
id id
createdAt createdAt
updatedAt updatedAt
commentableType
commentableId
companyId companyId
personId personId
} }
@ -3647,8 +3550,6 @@ export const RemoveActivityTargetsOnActivityDocument = gql`
id id
createdAt createdAt
updatedAt updatedAt
commentableType
commentableId
companyId companyId
personId personId
} }
@ -4263,23 +4164,10 @@ export type GetCompanyQueryResult = Apollo.QueryResult<GetCompanyQuery, GetCompa
export const UpdateOneCompanyDocument = gql` export const UpdateOneCompanyDocument = gql`
mutation UpdateOneCompany($where: CompanyWhereUniqueInput!, $data: CompanyUpdateInput!) { mutation UpdateOneCompany($where: CompanyWhereUniqueInput!, $data: CompanyUpdateInput!) {
updateOneCompany(data: $data, where: $where) { updateOneCompany(data: $data, where: $where) {
accountOwner { ...CompanyFieldsFragment
id
email
displayName
firstName
lastName
}
address
createdAt
domainName
employees
linkedinUrl
id
name
} }
} }
`; ${CompanyFieldsFragmentFragmentDoc}`;
export type UpdateOneCompanyMutationFn = Apollo.MutationFunction<UpdateOneCompanyMutation, UpdateOneCompanyMutationVariables>; export type UpdateOneCompanyMutationFn = Apollo.MutationFunction<UpdateOneCompanyMutation, UpdateOneCompanyMutationVariables>;
/** /**
@ -4310,10 +4198,10 @@ export type UpdateOneCompanyMutationOptions = Apollo.BaseMutationOptions<UpdateO
export const InsertOneCompanyDocument = gql` export const InsertOneCompanyDocument = gql`
mutation InsertOneCompany($data: CompanyCreateInput!) { mutation InsertOneCompany($data: CompanyCreateInput!) {
createOneCompany(data: $data) { createOneCompany(data: $data) {
...InsertCompanyFragment ...CompanyFieldsFragment
} }
} }
${InsertCompanyFragmentFragmentDoc}`; ${CompanyFieldsFragmentFragmentDoc}`;
export type InsertOneCompanyMutationFn = Apollo.MutationFunction<InsertOneCompanyMutation, InsertOneCompanyMutationVariables>; export type InsertOneCompanyMutationFn = Apollo.MutationFunction<InsertOneCompanyMutation, InsertOneCompanyMutationVariables>;
/** /**
@ -5489,12 +5377,10 @@ export type EmptyQueryQueryResult = Apollo.QueryResult<EmptyQueryQuery, EmptyQue
export const SearchCompanyDocument = gql` export const SearchCompanyDocument = gql`
query SearchCompany($where: CompanyWhereInput, $limit: Int, $orderBy: [CompanyOrderByWithRelationInput!]) { query SearchCompany($where: CompanyWhereInput, $limit: Int, $orderBy: [CompanyOrderByWithRelationInput!]) {
searchResults: findManyCompany(where: $where, take: $limit, orderBy: $orderBy) { searchResults: findManyCompany(where: $where, take: $limit, orderBy: $orderBy) {
id ...CompanyFieldsFragment
name
domainName
} }
} }
`; ${CompanyFieldsFragmentFragmentDoc}`;
/** /**
* __useSearchCompanyQuery__ * __useSearchCompanyQuery__
@ -5848,43 +5734,6 @@ export function useDeleteUserAccountMutation(baseOptions?: Apollo.MutationHookOp
export type DeleteUserAccountMutationHookResult = ReturnType<typeof useDeleteUserAccountMutation>; export type DeleteUserAccountMutationHookResult = ReturnType<typeof useDeleteUserAccountMutation>;
export type DeleteUserAccountMutationResult = Apollo.MutationResult<DeleteUserAccountMutation>; export type DeleteUserAccountMutationResult = Apollo.MutationResult<DeleteUserAccountMutation>;
export type DeleteUserAccountMutationOptions = Apollo.BaseMutationOptions<DeleteUserAccountMutation, DeleteUserAccountMutationVariables>; export type DeleteUserAccountMutationOptions = Apollo.BaseMutationOptions<DeleteUserAccountMutation, DeleteUserAccountMutationVariables>;
export const CreateViewFieldDocument = gql`
mutation CreateViewField($data: ViewFieldCreateInput!) {
createOneViewField(data: $data) {
id
fieldName
isVisible
sizeInPx
index
}
}
`;
export type CreateViewFieldMutationFn = Apollo.MutationFunction<CreateViewFieldMutation, CreateViewFieldMutationVariables>;
/**
* __useCreateViewFieldMutation__
*
* To run a mutation, you first call `useCreateViewFieldMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateViewFieldMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [createViewFieldMutation, { data, loading, error }] = useCreateViewFieldMutation({
* variables: {
* data: // value for 'data'
* },
* });
*/
export function useCreateViewFieldMutation(baseOptions?: Apollo.MutationHookOptions<CreateViewFieldMutation, CreateViewFieldMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<CreateViewFieldMutation, CreateViewFieldMutationVariables>(CreateViewFieldDocument, options);
}
export type CreateViewFieldMutationHookResult = ReturnType<typeof useCreateViewFieldMutation>;
export type CreateViewFieldMutationResult = Apollo.MutationResult<CreateViewFieldMutation>;
export type CreateViewFieldMutationOptions = Apollo.BaseMutationOptions<CreateViewFieldMutation, CreateViewFieldMutationVariables>;
export const CreateViewFieldsDocument = gql` export const CreateViewFieldsDocument = gql`
mutation CreateViewFields($data: [ViewFieldCreateManyInput!]!) { mutation CreateViewFields($data: [ViewFieldCreateManyInput!]!) {
createManyViewField(data: $data) { createManyViewField(data: $data) {

View File

@ -72,9 +72,7 @@ type OwnProps = {
'id' | 'firstName' | 'lastName' | 'displayName' 'id' | 'firstName' | 'lastName' | 'displayName'
> | null; > | null;
} & { } & {
activityTargets?: Array< activityTargets?: Array<Pick<ActivityTarget, 'id'>> | null;
Pick<ActivityTarget, 'id' | 'commentableId' | 'commentableType'>
> | null;
}; };
showComment?: boolean; showComment?: boolean;
autoFillTitle?: boolean; autoFillTitle?: boolean;

View File

@ -1,251 +0,0 @@
import { useCallback, useMemo, useState } from 'react';
import styled from '@emotion/styled';
import {
autoUpdate,
flip,
offset,
size,
useFloating,
} from '@floating-ui/react';
import { CompanyChip } from '@/companies/components/CompanyChip';
import { useFilteredSearchCompanyQuery } from '@/companies/queries';
import { PersonChip } from '@/people/components/PersonChip';
import { useFilteredSearchPeopleQuery } from '@/people/queries';
import { MultipleEntitySelect } from '@/ui/input/relation-picker/components/MultipleEntitySelect';
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { Activity, ActivityTarget, CommentableType } from '~/generated/graphql';
import { assertNotNull } from '~/utils/assert';
import { useHandleCheckableActivityTargetChange } from '../hooks/useHandleCheckableActivityTargetChange';
import { flatMapAndSortEntityForSelectArrayOfArrayByName } from '../utils/flatMapAndSortEntityForSelectArrayByName';
type OwnProps = {
activity?: Pick<Activity, 'id'> & {
activityTargets: Array<
Pick<
ActivityTarget,
'id' | 'commentableId' | 'commentableType' | 'companyId' | 'personId'
>
>;
};
};
const StyledContainer = styled.div`
align-items: flex-start;
display: flex;
flex-direction: row;
gap: ${({ theme }) => theme.spacing(2)};
justify-content: flex-start;
width: 100%;
`;
const StyledRelationContainer = styled.div`
--horizontal-padding: ${({ theme }) => theme.spacing(1)};
--vertical-padding: ${({ theme }) => theme.spacing(1.5)};
border: 1px solid transparent;
cursor: pointer;
display: flex;
flex-wrap: wrap;
gap: ${({ theme }) => theme.spacing(2)};
&:hover {
background-color: ${({ theme }) => theme.background.secondary};
border: 1px solid ${({ theme }) => theme.border.color.light};
}
min-height: calc(32px - 2 * var(--vertical-padding));
overflow: hidden;
padding: var(--vertical-padding) var(--horizontal-padding);
width: calc(100% - 2 * var(--horizontal-padding));
`;
const StyledMenuWrapper = styled.div`
z-index: ${({ theme }) => theme.lastLayerZIndex};
`;
export function ActivityRelationPicker({ activity }: OwnProps) {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [searchFilter, setSearchFilter] = useState('');
const [selectedEntityIds, setSelectedEntityIds] = useState<
Record<string, boolean>
>({});
const {
setHotkeyScopeAndMemorizePreviousScope,
goBackToPreviousHotkeyScope,
} = usePreviousHotkeyScope();
const initialPeopleIds = useMemo(
() =>
activity?.activityTargets
?.filter((relation) => relation.commentableType === 'Person')
.map((relation) => relation.personId || relation.commentableId)
.filter(assertNotNull) ?? [],
[activity?.activityTargets],
);
const initialCompanyIds = useMemo(
() =>
activity?.activityTargets
?.filter((relation) => relation.commentableType === 'Company')
.map((relation) => relation.companyId || relation.commentableId)
.filter(assertNotNull) ?? [],
[activity?.activityTargets],
);
const initialSelectedEntityIds = useMemo(
() =>
[...initialPeopleIds, ...initialCompanyIds].reduce<
Record<string, boolean>
>((result, entityId) => ({ ...result, [entityId]: true }), {}),
[initialPeopleIds, initialCompanyIds],
);
const personsForMultiSelect = useFilteredSearchPeopleQuery({
searchFilter,
selectedIds: initialPeopleIds,
});
const companiesForMultiSelect = useFilteredSearchCompanyQuery({
searchFilter,
selectedIds: initialCompanyIds,
});
const selectedEntities = flatMapAndSortEntityForSelectArrayOfArrayByName([
personsForMultiSelect.selectedEntities,
companiesForMultiSelect.selectedEntities,
]);
const filteredSelectedEntities =
flatMapAndSortEntityForSelectArrayOfArrayByName([
personsForMultiSelect.filteredSelectedEntities,
companiesForMultiSelect.filteredSelectedEntities,
]);
const entitiesToSelect = flatMapAndSortEntityForSelectArrayOfArrayByName([
personsForMultiSelect.entitiesToSelect,
companiesForMultiSelect.entitiesToSelect,
]);
const handleCheckItemsChange = useHandleCheckableActivityTargetChange({
activity,
});
const exitEditMode = useCallback(() => {
goBackToPreviousHotkeyScope();
setIsMenuOpen(false);
setSearchFilter('');
if (Object.values(selectedEntityIds).some((value) => !!value)) {
handleCheckItemsChange(selectedEntityIds, entitiesToSelect);
}
}, [
entitiesToSelect,
selectedEntityIds,
goBackToPreviousHotkeyScope,
handleCheckItemsChange,
]);
const handleRelationContainerClick = useCallback(() => {
if (isMenuOpen) {
exitEditMode();
} else {
setIsMenuOpen(true);
setSelectedEntityIds(initialSelectedEntityIds);
setHotkeyScopeAndMemorizePreviousScope(
RelationPickerHotkeyScope.RelationPicker,
);
}
}, [
initialSelectedEntityIds,
exitEditMode,
isMenuOpen,
setHotkeyScopeAndMemorizePreviousScope,
]);
useScopedHotkeys(
['esc', 'enter'],
() => {
exitEditMode();
},
RelationPickerHotkeyScope.RelationPicker,
[exitEditMode],
);
const { refs, floatingStyles } = useFloating({
strategy: 'absolute',
middleware: [
offset(({ rects }) => {
return -rects.reference.height;
}),
flip(),
size(),
],
whileElementsMounted: autoUpdate,
open: isMenuOpen,
placement: 'bottom-start',
});
useListenClickOutside({
refs: [refs.floating, refs.domReference],
callback: () => {
exitEditMode();
},
});
return (
<StyledContainer>
<StyledRelationContainer
ref={refs.setReference}
onClick={handleRelationContainerClick}
>
{selectedEntities?.map((entity) =>
entity.entityType === CommentableType.Company ? (
<CompanyChip
key={entity.id}
id={entity.id}
name={entity.name}
pictureUrl={entity.avatarUrl}
/>
) : (
<PersonChip
key={entity.id}
name={entity.name}
id={entity.id}
pictureUrl={entity.avatarUrl ?? ''}
/>
),
)}
</StyledRelationContainer>
{isMenuOpen && (
<RecoilScope>
<StyledMenuWrapper ref={refs.setFloating} style={floatingStyles}>
<MultipleEntitySelect
entities={{
entitiesToSelect,
filteredSelectedEntities,
selectedEntities,
loading: false, // TODO implement skeleton loading
}}
onChange={setSelectedEntityIds}
onSearchFilterChange={setSearchFilter}
searchFilter={searchFilter}
value={selectedEntityIds}
/>
</StyledMenuWrapper>
</RecoilScope>
)}
</StyledContainer>
);
}

View File

@ -2,7 +2,7 @@ import styled from '@emotion/styled';
import { CompanyChip } from '@/companies/components/CompanyChip'; import { CompanyChip } from '@/companies/components/CompanyChip';
import { PersonChip } from '@/people/components/PersonChip'; import { PersonChip } from '@/people/components/PersonChip';
import { GetCompaniesQuery, GetPeopleQuery } from '~/generated/graphql'; import { ActivityTarget, Company, Person } from '~/generated/graphql';
import { getLogoUrlFromDomainName } from '~/utils'; import { getLogoUrlFromDomainName } from '~/utils';
const StyledContainer = styled.div` const StyledContainer = styled.div`
@ -12,32 +12,44 @@ const StyledContainer = styled.div`
`; `;
export function ActivityTargetChips({ export function ActivityTargetChips({
targetCompanies, targets,
targetPeople,
}: { }: {
targetCompanies?: GetCompaniesQuery; targets?: Array<
targetPeople?: GetPeopleQuery; Pick<ActivityTarget, 'id'> & {
person?: Pick<Person, 'id' | 'displayName' | 'avatarUrl'> | null;
company?: Pick<Company, 'id' | 'domainName' | 'name'> | null;
}
> | null;
}) { }) {
if (!targets) {
return null;
}
return ( return (
<StyledContainer> <StyledContainer>
{targetCompanies?.companies && {targets.map(({ company, person }) => {
targetCompanies.companies.map((company) => ( if (company) {
<CompanyChip return (
key={company.id} <CompanyChip
id={company.id} key={company.id}
name={company.name} id={company.id}
pictureUrl={getLogoUrlFromDomainName(company.domainName)} name={company.name}
/> pictureUrl={getLogoUrlFromDomainName(company.domainName)}
))} />
{targetPeople?.people && );
targetPeople.people.map((person) => ( }
<PersonChip if (person) {
key={person.id} return (
id={person.id} <PersonChip
name={person.displayName} key={person.id}
pictureUrl={person.avatarUrl ?? ''} id={person.id}
/> name={person.displayName}
))} pictureUrl={person.avatarUrl ?? undefined}
/>
);
}
return <></>;
})}
</StyledContainer> </StyledContainer>
); );
} }

View File

@ -9,7 +9,6 @@ import {
CheckboxShape, CheckboxShape,
} from '@/ui/input/checkbox/components/Checkbox'; } from '@/ui/input/checkbox/components/Checkbox';
import { OverflowingTextWithTooltip } from '@/ui/tooltip/OverflowingTextWithTooltip'; import { OverflowingTextWithTooltip } from '@/ui/tooltip/OverflowingTextWithTooltip';
import { useGetCompaniesQuery, useGetPeopleQuery } from '~/generated/graphql';
import { beautifyExactDate } from '~/utils/date-utils'; import { beautifyExactDate } from '~/utils/date-utils';
import { useCompleteTask } from '../hooks/useCompleteTask'; import { useCompleteTask } from '../hooks/useCompleteTask';
@ -34,10 +33,13 @@ const StyledTaskBody = styled.div`
width: 1px; width: 1px;
`; `;
const StyledTaskTitle = styled.div` const StyledTaskTitle = styled.div<{
completed: boolean;
}>`
color: ${({ theme }) => theme.font.color.primary}; color: ${({ theme }) => theme.font.color.primary};
font-weight: ${({ theme }) => theme.font.weight.medium}; font-weight: ${({ theme }) => theme.font.weight.medium};
padding: 0 ${({ theme }) => theme.spacing(2)}; padding: 0 ${({ theme }) => theme.spacing(2)};
text-decoration: ${({ completed }) => (completed ? 'line-through' : 'none')};
`; `;
const StyledCommentIcon = styled.div` const StyledCommentIcon = styled.div`
@ -62,37 +64,7 @@ const StyledFieldsContainer = styled.div`
export function TaskRow({ task }: { task: TaskForList }) { export function TaskRow({ task }: { task: TaskForList }) {
const theme = useTheme(); const theme = useTheme();
const openActivityRightDrawer = useOpenActivityRightDrawer(); const openActivityRightDrawer = useOpenActivityRightDrawer();
const { data: targetPeople } = useGetPeopleQuery({
variables: {
where: {
id: {
in: task?.activityTargets
? task?.activityTargets
.filter((target) => target.commentableType === 'Person')
.map(
(target) => (target.personId || target.commentableId) ?? '',
)
: [],
},
},
},
});
const { data: targetCompanies } = useGetCompaniesQuery({
variables: {
where: {
id: {
in: task?.activityTargets
? task?.activityTargets
.filter((target) => target.commentableType === 'Company')
.map(
(target) => (target.companyId || target.commentableId) ?? '',
)
: [],
},
},
},
});
const body = JSON.parse(task.body ?? '{}')[0]?.content[0]?.text; const body = JSON.parse(task.body ?? '{}')[0]?.content[0]?.text;
const { completeTask } = useCompleteTask(task); const { completeTask } = useCompleteTask(task);
@ -113,7 +85,9 @@ export function TaskRow({ task }: { task: TaskForList }) {
onChange={completeTask} onChange={completeTask}
/> />
</div> </div>
<StyledTaskTitle>{task.title ?? '(No title)'}</StyledTaskTitle> <StyledTaskTitle completed={task.completedAt !== null}>
{task.title ?? '(No title)'}
</StyledTaskTitle>
<StyledTaskBody> <StyledTaskBody>
<OverflowingTextWithTooltip text={body} /> <OverflowingTextWithTooltip text={body} />
{task.comments && task.comments.length > 0 && ( {task.comments && task.comments.length > 0 && (
@ -123,10 +97,7 @@ export function TaskRow({ task }: { task: TaskForList }) {
)} )}
</StyledTaskBody> </StyledTaskBody>
<StyledFieldsContainer> <StyledFieldsContainer>
<ActivityTargetChips <ActivityTargetChips targets={task.activityTargets} />
targetCompanies={targetCompanies}
targetPeople={targetPeople}
/>
<StyledDueDate> <StyledDueDate>
<IconCalendar size={theme.icon.size.md} /> <IconCalendar size={theme.icon.size.md} />
{task.dueAt && beautifyExactDate(task.dueAt)} {task.dueAt && beautifyExactDate(task.dueAt)}

View File

@ -1,37 +0,0 @@
import { MemoryRouter } from 'react-router-dom';
import styled from '@emotion/styled';
import type { Meta, StoryObj } from '@storybook/react';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { mockedActivities } from '~/testing/mock-data/activities';
import { ActivityRelationPicker } from '../ActivityRelationPicker';
const StyledContainer = styled.div`
width: 400px;
`;
const meta: Meta<typeof ActivityRelationPicker> = {
title: 'Modules/Comments/ActivityRelationPicker',
component: ActivityRelationPicker,
decorators: [
(Story) => (
<MemoryRouter>
<StyledContainer>
<Story />
</StyledContainer>
</MemoryRouter>
),
ComponentDecorator,
],
args: { activity: mockedActivities[0] },
parameters: {
msw: graphqlMocks,
},
};
export default meta;
type Story = StoryObj<typeof ActivityRelationPicker>;
export const Default: Story = {};

View File

@ -4,59 +4,22 @@ import { FieldContext } from '@/ui/editable-field/states/FieldContext';
import { IconArrowUpRight } from '@/ui/icon'; import { IconArrowUpRight } from '@/ui/icon';
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope'; import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { import { Activity, ActivityTarget, Company, Person } from '~/generated/graphql';
Activity,
ActivityTarget,
useGetCompaniesQuery,
useGetPeopleQuery,
} from '~/generated/graphql';
import { ActivityRelationEditableFieldEditMode } from './ActivityRelationEditableFieldEditMode'; import { ActivityRelationEditableFieldEditMode } from './ActivityRelationEditableFieldEditMode';
type OwnProps = { type OwnProps = {
activity?: Pick<Activity, 'id'> & { activity?: Pick<Activity, 'id'> & {
activityTargets?: Array< activityTargets?: Array<
Pick< Pick<ActivityTarget, 'id' | 'personId' | 'companyId'> & {
ActivityTarget, person?: Pick<Person, 'id' | 'displayName'>;
'id' | 'commentableId' | 'commentableType' | 'personId' | 'companyId' company?: Pick<Company, 'id' | 'domainName' | 'name'>;
> }
> | null; > | null;
}; };
}; };
export function ActivityRelationEditableField({ activity }: OwnProps) { export function ActivityRelationEditableField({ activity }: OwnProps) {
const { data: targetPeople } = useGetPeopleQuery({
variables: {
where: {
id: {
in: activity?.activityTargets
? activity?.activityTargets
.filter((target) => target.commentableType === 'Person')
.map(
(target) => (target.personId || target.commentableId) ?? '',
)
: [],
},
},
},
});
const { data: targetCompanies } = useGetCompaniesQuery({
variables: {
where: {
id: {
in: activity?.activityTargets
? activity?.activityTargets
.filter((target) => target.commentableType === 'Company')
.map(
(target) => (target.companyId || target.commentableId) ?? '',
)
: [],
},
},
},
});
return ( return (
<RecoilScope SpecificContext={FieldContext}> <RecoilScope SpecificContext={FieldContext}>
<RecoilScope> <RecoilScope>
@ -71,10 +34,7 @@ export function ActivityRelationEditableField({ activity }: OwnProps) {
} }
label="Relations" label="Relations"
displayModeContent={ displayModeContent={
<ActivityTargetChips <ActivityTargetChips targets={activity?.activityTargets} />
targetCompanies={targetCompanies}
targetPeople={targetPeople}
/>
} }
/> />
</RecoilScope> </RecoilScope>

View File

@ -13,10 +13,7 @@ import { assertNotNull } from '~/utils/assert';
type OwnProps = { type OwnProps = {
activity?: Pick<Activity, 'id'> & { activity?: Pick<Activity, 'id'> & {
activityTargets?: Array< activityTargets?: Array<
Pick< Pick<ActivityTarget, 'id' | 'personId' | 'companyId'>
ActivityTarget,
'id' | 'commentableId' | 'commentableType' | 'personId' | 'companyId'
>
> | null; > | null;
}; };
}; };
@ -33,8 +30,8 @@ export function ActivityRelationEditableFieldEditMode({ activity }: OwnProps) {
const initialPeopleIds = useMemo( const initialPeopleIds = useMemo(
() => () =>
activity?.activityTargets activity?.activityTargets
?.filter((relation) => relation.commentableType === 'Person') ?.filter((relation) => relation.personId !== null)
.map((relation) => relation.personId || relation.commentableId) .map((relation) => relation.personId)
.filter(assertNotNull) ?? [], .filter(assertNotNull) ?? [],
[activity?.activityTargets], [activity?.activityTargets],
); );
@ -42,8 +39,8 @@ export function ActivityRelationEditableFieldEditMode({ activity }: OwnProps) {
const initialCompanyIds = useMemo( const initialCompanyIds = useMemo(
() => () =>
activity?.activityTargets activity?.activityTargets
?.filter((relation) => relation.commentableType === 'Company') ?.filter((relation) => relation.companyId !== null)
.map((relation) => relation.companyId || relation.commentableId) .map((relation) => relation.companyId)
.filter(assertNotNull) ?? [], .filter(assertNotNull) ?? [],
[activity?.activityTargets], [activity?.activityTargets],
); );

View File

@ -6,20 +6,20 @@ import { GET_PEOPLE } from '@/people/queries';
import { import {
Activity, Activity,
ActivityTarget, ActivityTarget,
CommentableType,
useAddActivityTargetsOnActivityMutation, useAddActivityTargetsOnActivityMutation,
useRemoveActivityTargetsOnActivityMutation, useRemoveActivityTargetsOnActivityMutation,
} from '~/generated/graphql'; } from '~/generated/graphql';
import { GET_ACTIVITY } from '../queries'; import { GET_ACTIVITY } from '../queries';
import { CommentableEntityForSelect } from '../types/CommentableEntityForSelect'; import { ActivityTargetableEntityType } from '../types/ActivityTargetableEntity';
import { ActivityTargetableEntityForSelect } from '../types/ActivityTargetableEntityForSelect';
export function useHandleCheckableActivityTargetChange({ export function useHandleCheckableActivityTargetChange({
activity, activity,
}: { }: {
activity?: Pick<Activity, 'id'> & { activity?: Pick<Activity, 'id'> & {
activityTargets?: Array< activityTargets?: Array<
Pick<ActivityTarget, 'id' | 'commentableId' | 'commentableType'> Pick<ActivityTarget, 'id' | 'personId' | 'companyId'>
> | null; > | null;
}; };
}) { }) {
@ -43,14 +43,16 @@ export function useHandleCheckableActivityTargetChange({
return async function handleCheckItemsChange( return async function handleCheckItemsChange(
entityValues: Record<string, boolean>, entityValues: Record<string, boolean>,
entities: CommentableEntityForSelect[], entities: ActivityTargetableEntityForSelect[],
) { ) {
if (!activity) { if (!activity) {
return; return;
} }
const currentEntityIds = activity.activityTargets const currentEntityIds = activity.activityTargets
? activity.activityTargets.map(({ commentableId }) => commentableId) ? activity.activityTargets.map(
({ personId, companyId }) => personId ?? companyId,
)
: []; : [];
const entitiesToAdd = entities.filter( const entitiesToAdd = entities.filter(
@ -64,12 +66,14 @@ export function useHandleCheckableActivityTargetChange({
activityTargetInputs: entitiesToAdd.map((entity) => ({ activityTargetInputs: entitiesToAdd.map((entity) => ({
id: v4(), id: v4(),
createdAt: new Date().toISOString(), createdAt: new Date().toISOString(),
commentableType: entity.entityType,
commentableId: entity.id,
companyId: companyId:
entity.entityType === CommentableType.Company ? entity.id : null, entity.entityType === ActivityTargetableEntityType.Company
? entity.id
: null,
personId: personId:
entity.entityType === CommentableType.Person ? entity.id : null, entity.entityType === ActivityTargetableEntityType.Person
? entity.id
: null,
})), })),
}, },
}); });
@ -77,8 +81,9 @@ export function useHandleCheckableActivityTargetChange({
const activityTargetIdsToDelete = activity.activityTargets const activityTargetIdsToDelete = activity.activityTargets
? activity.activityTargets ? activity.activityTargets
.filter( .filter(
({ commentableId }) => ({ personId, companyId }) =>
commentableId && !entityValues[commentableId], (personId ?? companyId) &&
!entityValues[personId ?? companyId ?? ''],
) )
.map(({ id }) => id) .map(({ id }) => id)
: []; : [];

View File

@ -9,20 +9,19 @@ import { useRightDrawer } from '@/ui/right-drawer/hooks/useRightDrawer';
import { RightDrawerHotkeyScope } from '@/ui/right-drawer/types/RightDrawerHotkeyScope'; import { RightDrawerHotkeyScope } from '@/ui/right-drawer/types/RightDrawerHotkeyScope';
import { RightDrawerPages } from '@/ui/right-drawer/types/RightDrawerPages'; import { RightDrawerPages } from '@/ui/right-drawer/types/RightDrawerPages';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { import { ActivityType, useCreateActivityMutation } from '~/generated/graphql';
ActivityType,
CommentableType,
useCreateActivityMutation,
} from '~/generated/graphql';
import { import {
GET_ACTIVITIES, GET_ACTIVITIES,
GET_ACTIVITIES_BY_TARGETS, GET_ACTIVITIES_BY_TARGETS,
GET_ACTIVITY, GET_ACTIVITY,
} from '../queries'; } from '../queries';
import { commentableEntityArrayState } from '../states/commentableEntityArrayState'; import { activityTargetableEntityArrayState } from '../states/activityTargetableEntityArrayState';
import { viewableActivityIdState } from '../states/viewableActivityIdState'; import { viewableActivityIdState } from '../states/viewableActivityIdState';
import { CommentableEntity } from '../types/CommentableEntity'; import {
ActivityTargetableEntity,
ActivityTargetableEntityType,
} from '../types/ActivityTargetableEntity';
export function useOpenCreateActivityDrawer() { export function useOpenCreateActivityDrawer() {
const { openRightDrawer } = useRightDrawer(); const { openRightDrawer } = useRightDrawer();
@ -30,14 +29,14 @@ export function useOpenCreateActivityDrawer() {
const currentUser = useRecoilValue(currentUserState); const currentUser = useRecoilValue(currentUserState);
const setHotkeyScope = useSetHotkeyScope(); const setHotkeyScope = useSetHotkeyScope();
const [, setCommentableEntityArray] = useRecoilState( const [, setActivityTargetableEntityArray] = useRecoilState(
commentableEntityArrayState, activityTargetableEntityArrayState,
); );
const [, setViewableActivityId] = useRecoilState(viewableActivityIdState); const [, setViewableActivityId] = useRecoilState(viewableActivityIdState);
return function openCreateActivityDrawer( return function openCreateActivityDrawer(
type: ActivityType, type: ActivityType,
entities?: CommentableEntity[], entities?: ActivityTargetableEntity[],
) { ) {
const now = new Date().toISOString(); const now = new Date().toISOString();
@ -54,14 +53,14 @@ export function useOpenCreateActivityDrawer() {
createMany: { createMany: {
data: entities data: entities
? entities.map((entity) => ({ ? entities.map((entity) => ({
commentableId: entity.id,
commentableType: entity.type,
companyId: companyId:
entity.type === CommentableType.Company entity.type === ActivityTargetableEntityType.Company
? entity.id ? entity.id
: null, : null,
personId: personId:
entity.type === CommentableType.Person ? entity.id : null, entity.type === ActivityTargetableEntityType.Person
? entity.id
: null,
id: v4(), id: v4(),
createdAt: now, createdAt: now,
})) }))
@ -81,7 +80,7 @@ export function useOpenCreateActivityDrawer() {
onCompleted(data) { onCompleted(data) {
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false }); setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
setViewableActivityId(data.createOneActivity.id); setViewableActivityId(data.createOneActivity.id);
setCommentableEntityArray(entities ?? []); setActivityTargetableEntityArray(entities ?? []);
openRightDrawer(RightDrawerPages.CreateActivity); openRightDrawer(RightDrawerPages.CreateActivity);
}, },
}); });

View File

@ -1,9 +1,12 @@
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { selectedRowIdsSelector } from '@/ui/table/states/selectedRowIdsSelector'; import { selectedRowIdsSelector } from '@/ui/table/states/selectedRowIdsSelector';
import { ActivityType, CommentableType } from '~/generated/graphql'; import { ActivityType } from '~/generated/graphql';
import { CommentableEntity } from '../types/CommentableEntity'; import {
ActivityTargetableEntity,
ActivityTargetableEntityType,
} from '../types/ActivityTargetableEntity';
import { useOpenCreateActivityDrawer } from './useOpenCreateActivityDrawer'; import { useOpenCreateActivityDrawer } from './useOpenCreateActivityDrawer';
@ -14,14 +17,13 @@ export function useOpenCreateActivityDrawerForSelectedRowIds() {
return function openCreateCommentDrawerForSelectedRowIds( return function openCreateCommentDrawerForSelectedRowIds(
type: ActivityType, type: ActivityType,
entityType: CommentableType, entityType: ActivityTargetableEntityType,
) { ) {
const commentableEntityArray: CommentableEntity[] = selectedEntityIds.map( const activityTargetableEntityArray: ActivityTargetableEntity[] =
(id) => ({ selectedEntityIds.map((id) => ({
type: entityType, type: entityType,
id, id,
}), }));
); openCreateActivityDrawer(type, activityTargetableEntityArray);
openCreateActivityDrawer(type, commentableEntityArray);
}; };
} }

View File

@ -5,22 +5,22 @@ import { RightDrawerHotkeyScope } from '@/ui/right-drawer/types/RightDrawerHotke
import { RightDrawerPages } from '@/ui/right-drawer/types/RightDrawerPages'; import { RightDrawerPages } from '@/ui/right-drawer/types/RightDrawerPages';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { commentableEntityArrayState } from '../states/commentableEntityArrayState'; import { activityTargetableEntityArrayState } from '../states/activityTargetableEntityArrayState';
import { CommentableEntity } from '../types/CommentableEntity'; import { ActivityTargetableEntity } from '../types/ActivityTargetableEntity';
// TODO: refactor with recoil callback to avoid rerender // TODO: refactor with recoil callback to avoid rerender
export function useOpenTimelineRightDrawer() { export function useOpenTimelineRightDrawer() {
const { openRightDrawer } = useRightDrawer(); const { openRightDrawer } = useRightDrawer();
const [, setCommentableEntityArray] = useRecoilState( const [, setActivityTargetableEntityArray] = useRecoilState(
commentableEntityArrayState, activityTargetableEntityArrayState,
); );
const setHotkeyScope = useSetHotkeyScope(); const setHotkeyScope = useSetHotkeyScope();
return function openTimelineRightDrawer( return function openTimelineRightDrawer(
commentableEntityArray: CommentableEntity[], activityTargetableEntityArray: ActivityTargetableEntity[],
) { ) {
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false }); setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
setCommentableEntityArray(commentableEntityArray); setActivityTargetableEntityArray(activityTargetableEntityArray);
openRightDrawer(RightDrawerPages.Timeline); openRightDrawer(RightDrawerPages.Timeline);
}; };
} }

View File

@ -45,8 +45,6 @@ export const CREATE_ACTIVITY_WITH_COMMENT = gql`
createdAt createdAt
updatedAt updatedAt
activityId activityId
commentableType
commentableId
companyId companyId
personId personId
} }

View File

@ -1,5 +1,58 @@
import { gql } from '@apollo/client'; import { gql } from '@apollo/client';
export const ACTIVITY_QUERY_FRAGMENT = gql`
fragment ActivityQueryFragment on Activity {
id
createdAt
title
body
type
completedAt
dueAt
assignee {
id
firstName
lastName
displayName
avatarUrl
}
author {
id
firstName
lastName
displayName
}
comments {
id
body
createdAt
updatedAt
author {
id
displayName
firstName
lastName
avatarUrl
}
}
activityTargets {
id
companyId
personId
company {
id
name
domainName
}
person {
id
displayName
avatarUrl
}
}
}
`;
export const GET_ACTIVITIES_BY_TARGETS = gql` export const GET_ACTIVITIES_BY_TARGETS = gql`
query GetActivitiesByTargets( query GetActivitiesByTargets(
$activityTargetIds: [String!]! $activityTargetIds: [String!]!
@ -8,49 +61,17 @@ export const GET_ACTIVITIES_BY_TARGETS = gql`
findManyActivities( findManyActivities(
orderBy: $orderBy orderBy: $orderBy
where: { where: {
activityTargets: { some: { commentableId: { in: $activityTargetIds } } } activityTargets: {
} some: {
) { OR: [
id { personId: { in: $activityTargetIds } }
createdAt { companyId: { in: $activityTargetIds } }
title ]
body }
type
completedAt
dueAt
assignee {
id
firstName
lastName
displayName
avatarUrl
}
author {
id
firstName
lastName
displayName
}
comments {
id
body
createdAt
updatedAt
author {
id
displayName
firstName
lastName
avatarUrl
} }
} }
activityTargets { ) {
id ...ActivityQueryFragment
commentableType
commentableId
companyId
personId
}
} }
} }
`; `;
@ -61,36 +82,7 @@ export const GET_ACTIVITIES = gql`
$orderBy: [ActivityOrderByWithRelationInput!] $orderBy: [ActivityOrderByWithRelationInput!]
) { ) {
findManyActivities(orderBy: $orderBy, where: $where) { findManyActivities(orderBy: $orderBy, where: $where) {
id ...ActivityQueryFragment
createdAt
title
body
type
completedAt
dueAt
assignee {
id
firstName
lastName
displayName
avatarUrl
}
author {
id
firstName
lastName
displayName
}
comments {
id
}
activityTargets {
id
commentableType
commentableId
companyId
personId
}
} }
} }
`; `;
@ -98,46 +90,7 @@ export const GET_ACTIVITIES = gql`
export const GET_ACTIVITY = gql` export const GET_ACTIVITY = gql`
query GetActivity($activityId: String!) { query GetActivity($activityId: String!) {
findManyActivities(where: { id: { equals: $activityId } }) { findManyActivities(where: { id: { equals: $activityId } }) {
id ...ActivityQueryFragment
createdAt
body
title
type
completedAt
dueAt
assignee {
id
firstName
lastName
displayName
avatarUrl
}
author {
id
firstName
lastName
displayName
}
comments {
id
body
createdAt
updatedAt
author {
id
displayName
firstName
lastName
avatarUrl
}
}
activityTargets {
id
commentableType
commentableId
companyId
personId
}
} }
} }
`; `;

View File

@ -16,8 +16,6 @@ export const ADD_ACTIVITY_TARGETS = gql`
id id
createdAt createdAt
updatedAt updatedAt
commentableType
commentableId
companyId companyId
personId personId
} }
@ -43,8 +41,6 @@ export const REMOVE_ACTIVITY_TARGETS = gql`
id id
createdAt createdAt
updatedAt updatedAt
commentableType
commentableId
companyId companyId
personId personId
} }

View File

@ -1,19 +1,21 @@
import { useRecoilState } from 'recoil'; import { useRecoilValue } from 'recoil';
import { commentableEntityArrayState } from '@/activities/states/commentableEntityArrayState'; import { activityTargetableEntityArrayState } from '@/activities/states/activityTargetableEntityArrayState';
import { Timeline } from '@/activities/timeline/components/Timeline'; import { Timeline } from '@/activities/timeline/components/Timeline';
export function RightDrawerTimeline() { export function RightDrawerTimeline() {
const [commentableEntityArray] = useRecoilState(commentableEntityArrayState); const activityTargetableEntityArray = useRecoilValue(
activityTargetableEntityArrayState,
);
return ( return (
<> <>
{commentableEntityArray.map((commentableEntity) => ( {activityTargetableEntityArray.map((targetableEntity) => (
<Timeline <Timeline
key={commentableEntity.id} key={targetableEntity.id}
entity={{ entity={{
id: commentableEntity?.id ?? '', id: targetableEntity?.id ?? '',
type: commentableEntity.type, type: targetableEntity.type,
}} }}
/> />
))} ))}

View File

@ -0,0 +1,10 @@
import { atom } from 'recoil';
import { ActivityTargetableEntity } from '../types/ActivityTargetableEntity';
export const activityTargetableEntityArrayState = atom<
ActivityTargetableEntity[]
>({
key: 'activities/targetable-entity-array',
default: [],
});

View File

@ -1,8 +0,0 @@
import { atom } from 'recoil';
import { CommentableEntity } from '../types/CommentableEntity';
export const commentableEntityArrayState = atom<CommentableEntity[]>({
key: 'activities/commentable-entity-array',
default: [],
});

View File

@ -5,7 +5,7 @@ import styled from '@emotion/styled';
import { ActivityCreateButton } from '@/activities/components/ActivityCreateButton'; import { ActivityCreateButton } from '@/activities/components/ActivityCreateButton';
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer'; import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
import { ActivityForDrawer } from '@/activities/types/ActivityForDrawer'; import { ActivityForDrawer } from '@/activities/types/ActivityForDrawer';
import { CommentableEntity } from '@/activities/types/CommentableEntity'; import { ActivityTargetableEntity } from '@/activities/types/ActivityTargetableEntity';
import { IconCircleDot } from '@/ui/icon'; import { IconCircleDot } from '@/ui/icon';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { import {
@ -93,7 +93,7 @@ const StyledStartIcon = styled.div`
width: 20px; width: 20px;
`; `;
export function Timeline({ entity }: { entity: CommentableEntity }) { export function Timeline({ entity }: { entity: ActivityTargetableEntity }) {
const theme = useTheme(); const theme = useTheme();
const { data: queryResult, loading } = useGetActivitiesByTargetsQuery({ const { data: queryResult, loading } = useGetActivitiesByTargetsQuery({

View File

@ -0,0 +1,9 @@
export enum ActivityTargetableEntityType {
Person = 'Person',
Company = 'Company',
}
export type ActivityTargetableEntity = {
id: string;
type: ActivityTargetableEntityType;
};

View File

@ -0,0 +1,7 @@
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { ActivityTargetableEntityType } from './ActivityTargetableEntity';
export type ActivityTargetableEntityForSelect = EntityForSelect & {
entityType: ActivityTargetableEntityType;
};

View File

@ -1,6 +0,0 @@
import { CommentableType } from '~/generated/graphql';
export type CommentableEntity = {
id: string;
type: CommentableType;
};

View File

@ -1,6 +0,0 @@
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { CommentableType } from '~/generated/graphql';
export type CommentableEntityForSelect = EntityForSelect & {
entityType: CommentableType;
};

View File

@ -92,43 +92,6 @@ export function CommandMenu() {
cmd.type === CommandType.Create, cmd.type === CommandType.Create,
); );
/*
TODO: Allow performing actions on page through CommandBar
import { useMatch, useResolvedPath } from 'react-router-dom';
import { IconBuildingSkyscraper, IconUser } from '@/ui/icon';
const createSection = (
<StyledGroup heading="Create">
<CommandMenuItem
label="Create People"
onClick={createPeople}
icon={<IconUser />}
shortcuts={
!!useMatch({
path: useResolvedPath('/people').pathname,
end: true,
})
? ['C']
: []
}
/>
<CommandMenuItem
label="Create Company"
onClick={createCompany}
icon={<IconBuildingSkyscraper />}
shortcuts={
!!useMatch({
path: useResolvedPath('/companies').pathname,
end: true,
})
? ['C']
: []
}
/>
</StyledGroup>
);*/
return ( return (
<StyledDialog <StyledDialog
open={isCommandMenuOpened} open={isCommandMenuOpened}
@ -181,6 +144,7 @@ export function CommandMenu() {
to={matchingNavigateCommand.to} to={matchingNavigateCommand.to}
label={matchingNavigateCommand.label} label={matchingNavigateCommand.label}
shortcuts={matchingNavigateCommand.shortcuts} shortcuts={matchingNavigateCommand.shortcuts}
key={matchingNavigateCommand.label}
/> />
</StyledGroup> </StyledGroup>
)} )}
@ -245,7 +209,7 @@ export function CommandMenu() {
) )
.map((cmd) => ( .map((cmd) => (
<CommandMenuItem <CommandMenuItem
key={cmd.shortcuts?.join('')} key={cmd.shortcuts?.join('') ?? ''}
to={cmd.to} to={cmd.to}
label={cmd.label} label={cmd.label}
shortcuts={cmd.shortcuts} shortcuts={cmd.shortcuts}

View File

@ -17,6 +17,7 @@ import {
export type OwnProps = { export type OwnProps = {
label: string; label: string;
to?: string; to?: string;
key: string;
onClick?: () => void; onClick?: () => void;
icon?: ReactNode; icon?: ReactNode;
shortcuts?: Array<string>; shortcuts?: Array<string>;

View File

@ -1,9 +1,9 @@
import { ReactNode, useContext } from 'react'; import { ReactNode, useContext } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; import { useRecoilState, useRecoilValue } from 'recoil';
import { useCurrentCardSelected } from '@/ui/board/hooks/useCurrentCardSelected';
import { BoardCardIdContext } from '@/ui/board/states/BoardCardIdContext'; import { BoardCardIdContext } from '@/ui/board/states/BoardCardIdContext';
import { selectedBoardCardIdsState } from '@/ui/board/states/selectedBoardCardIdsState';
import { viewFieldsDefinitionsState } from '@/ui/board/states/viewFieldsDefinitionsState'; import { viewFieldsDefinitionsState } from '@/ui/board/states/viewFieldsDefinitionsState';
import { EntityChipVariant } from '@/ui/chip/components/EntityChip'; import { EntityChipVariant } from '@/ui/chip/components/EntityChip';
import { GenericEditableField } from '@/ui/editable-field/components/GenericEditableField'; import { GenericEditableField } from '@/ui/editable-field/components/GenericEditableField';
@ -14,7 +14,6 @@ import {
Checkbox, Checkbox,
CheckboxVariant, CheckboxVariant,
} from '@/ui/input/checkbox/components/Checkbox'; } from '@/ui/input/checkbox/components/Checkbox';
import { actionBarOpenState } from '@/ui/table/states/ActionBarIsOpenState';
import { useUpdateOnePipelineProgressMutation } from '~/generated/graphql'; import { useUpdateOnePipelineProgressMutation } from '~/generated/graphql';
import { getLogoUrlFromDomainName } from '~/utils'; import { getLogoUrlFromDomainName } from '~/utils';
@ -41,7 +40,7 @@ const StyledBoardCard = styled.div<{ selected: boolean }>`
cursor: pointer; cursor: pointer;
.checkbox-container { .checkbox-container {
opacity: 0; opacity: ${({ selected }) => (selected ? 1 : 0)};
} }
&:hover .checkbox-container { &:hover .checkbox-container {
@ -103,6 +102,8 @@ const StyledFieldContainer = styled.div`
`; `;
export function CompanyBoardCard() { export function CompanyBoardCard() {
const { currentCardSelected, setCurrentCardSelected } =
useCurrentCardSelected();
const boardCardId = useContext(BoardCardIdContext); const boardCardId = useContext(BoardCardIdContext);
const [companyProgress] = useRecoilState( const [companyProgress] = useRecoilState(
@ -110,25 +111,8 @@ export function CompanyBoardCard() {
); );
const { pipelineProgress, company } = companyProgress ?? {}; const { pipelineProgress, company } = companyProgress ?? {};
const [selectedBoardCards, setSelectedBoardCards] = useRecoilState(
selectedBoardCardIdsState,
);
const viewFieldsDefinitions = useRecoilValue(viewFieldsDefinitionsState); const viewFieldsDefinitions = useRecoilValue(viewFieldsDefinitionsState);
const selected = selectedBoardCards.includes(boardCardId ?? '');
const setActionBarOpenState = useSetRecoilState(actionBarOpenState);
function setSelected(isSelected: boolean) {
if (isSelected) {
setSelectedBoardCards([...selectedBoardCards, boardCardId ?? '']);
setActionBarOpenState(true);
} else {
setSelectedBoardCards(
selectedBoardCards.filter((id) => id !== boardCardId),
);
}
}
// boardCardId check can be moved to a wrapper to avoid unnecessary logic above // boardCardId check can be moved to a wrapper to avoid unnecessary logic above
if (!company || !pipelineProgress || !boardCardId) { if (!company || !pipelineProgress || !boardCardId) {
return null; return null;
@ -153,8 +137,8 @@ export function CompanyBoardCard() {
return ( return (
<StyledBoardCardWrapper> <StyledBoardCardWrapper>
<StyledBoardCard <StyledBoardCard
selected={selected} selected={currentCardSelected}
onClick={() => setSelected(!selected)} onClick={() => setCurrentCardSelected(!currentCardSelected)}
> >
<StyledBoardCardHeader> <StyledBoardCardHeader>
<CompanyChip <CompanyChip
@ -165,8 +149,8 @@ export function CompanyBoardCard() {
/> />
<StyledCheckboxContainer className="checkbox-container"> <StyledCheckboxContainer className="checkbox-container">
<Checkbox <Checkbox
checked={selected} checked={currentCardSelected}
onChange={() => setSelected(!selected)} onChange={() => setCurrentCardSelected(!currentCardSelected)}
variant={CheckboxVariant.Secondary} variant={CheckboxVariant.Secondary}
/> />
</StyledCheckboxContainer> </StyledCheckboxContainer>

View File

@ -0,0 +1,41 @@
import { SingleEntitySelect } from '@/ui/input/relation-picker/components/SingleEntitySelect';
import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState';
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { useFilteredSearchCompanyQuery } from '../queries';
export type OwnProps = {
companyId: string | null;
onSubmit: (newCompanyId: EntityForSelect | null) => void;
onCancel?: () => void;
};
export function CompanyPicker({ companyId, onSubmit, onCancel }: OwnProps) {
const [searchFilter] = useRecoilScopedState(
relationPickerSearchFilterScopedState,
);
const companies = useFilteredSearchCompanyQuery({
searchFilter,
selectedIds: companyId ? [companyId] : [],
});
async function handleEntitySelected(
selectedCompany: EntityForSelect | null | undefined,
) {
onSubmit(selectedCompany ?? null);
}
return (
<SingleEntitySelect
onEntitySelected={handleEntitySelected}
onCancel={onCancel}
entities={{
loading: companies.loading,
entitiesToSelect: companies.entitiesToSelect,
selectedEntity: companies.selectedEntities[0],
}}
/>
);
}

View File

@ -4,8 +4,10 @@ import { useRecoilState, useSetRecoilState } from 'recoil';
import { pipelineViewFields } from '@/pipeline/constants/pipelineViewFields'; import { pipelineViewFields } from '@/pipeline/constants/pipelineViewFields';
import { isBoardLoadedState } from '@/ui/board/states/isBoardLoadedState'; import { isBoardLoadedState } from '@/ui/board/states/isBoardLoadedState';
import { viewFieldsDefinitionsState } from '@/ui/board/states/viewFieldsDefinitionsState'; import { viewFieldsDefinitionsState } from '@/ui/board/states/viewFieldsDefinitionsState';
import { availableFiltersScopedState } from '@/ui/filter-n-sort/states/availableFiltersScopedState';
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState'; import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIntoWhereClause'; import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIntoWhereClause';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { import {
PipelineProgressableType, PipelineProgressableType,
@ -17,6 +19,7 @@ import {
useGetPipelineProgressQuery, useGetPipelineProgressQuery,
useGetPipelinesQuery, useGetPipelinesQuery,
} from '~/generated/graphql'; } from '~/generated/graphql';
import { opportunitiesBoardOptions } from '~/pages/opportunities/opportunitiesBoardOptions';
import { useUpdateCompanyBoardCardIds } from '../hooks/useUpdateBoardCardIds'; import { useUpdateCompanyBoardCardIds } from '../hooks/useUpdateBoardCardIds';
import { useUpdateCompanyBoard } from '../hooks/useUpdateCompanyBoardColumns'; import { useUpdateCompanyBoard } from '../hooks/useUpdateCompanyBoardColumns';
@ -30,8 +33,13 @@ export function HooksCompanyBoard({
const setFieldsDefinitionsState = useSetRecoilState( const setFieldsDefinitionsState = useSetRecoilState(
viewFieldsDefinitionsState, viewFieldsDefinitionsState,
); );
const [, setAvailableFilters] = useRecoilScopedState(
availableFiltersScopedState,
CompanyBoardContext,
);
useEffect(() => { useEffect(() => {
setAvailableFilters(opportunitiesBoardOptions.filters);
setFieldsDefinitionsState(pipelineViewFields); setFieldsDefinitionsState(pipelineViewFields);
}); });

View File

@ -1,46 +0,0 @@
import { EditableField } from '@/ui/editable-field/components/EditableField';
import { FieldContext } from '@/ui/editable-field/states/FieldContext';
import { IconUserCircle } from '@/ui/icon';
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { UserChip } from '@/users/components/UserChip';
import { Company, User } from '~/generated/graphql';
import { CompanyAccountOwnerPickerFieldEditMode } from './CompanyAccountOwnerPickerFieldEditMode';
type OwnProps = {
company: Pick<Company, 'id' | 'accountOwnerId'> & {
accountOwner?: Pick<User, 'id' | 'displayName' | 'avatarUrl'> | null;
};
};
export function CompanyAccountOwnerEditableField({ company }: OwnProps) {
return (
<RecoilScope SpecificContext={FieldContext}>
<RecoilScope>
<EditableField
customEditHotkeyScope={{
scope: RelationPickerHotkeyScope.RelationPicker,
}}
iconLabel={<IconUserCircle />}
editModeContent={
<CompanyAccountOwnerPickerFieldEditMode company={company} />
}
displayModeContent={
company.accountOwner?.displayName ? (
<UserChip
id={company.accountOwner.id}
name={company.accountOwner?.displayName ?? ''}
pictureUrl={company.accountOwner?.avatarUrl ?? ''}
/>
) : (
<></>
)
}
isDisplayModeContentEmpty={!company.accountOwner}
isDisplayModeFixHeight={true}
/>
</RecoilScope>
</RecoilScope>
);
}

View File

@ -1,47 +0,0 @@
import styled from '@emotion/styled';
import { CompanyAccountOwnerPicker } from '@/companies/components/CompanyAccountOwnerPicker';
import { useEditableField } from '@/ui/editable-field/hooks/useEditableField';
import { Company, User } from '~/generated/graphql';
const StyledContainer = styled.div`
left: 0px;
position: absolute;
top: -8px;
`;
export type OwnProps = {
company: Pick<Company, 'id'> & {
accountOwner?: Pick<User, 'id' | 'displayName'> | null;
};
onSubmit?: () => void;
onCancel?: () => void;
};
export function CompanyAccountOwnerPickerFieldEditMode({
company,
onSubmit,
onCancel,
}: OwnProps) {
const { closeEditableField } = useEditableField();
function handleSubmit() {
closeEditableField();
onSubmit?.();
}
function handleCancel() {
closeEditableField();
onCancel?.();
}
return (
<StyledContainer>
<CompanyAccountOwnerPicker
company={company}
onCancel={handleCancel}
onSubmit={handleSubmit}
/>
</StyledContainer>
);
}

View File

@ -1,65 +0,0 @@
import { useEffect, useState } from 'react';
import { EditableField } from '@/ui/editable-field/components/EditableField';
import { FieldContext } from '@/ui/editable-field/states/FieldContext';
import { IconMap } from '@/ui/icon';
import { TextInputEdit } from '@/ui/input/text/components/TextInputEdit';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { Company, useUpdateOneCompanyMutation } from '~/generated/graphql';
type OwnProps = {
company: Pick<Company, 'id' | 'address'>;
};
export function CompanyAddressEditableField({ company }: OwnProps) {
const [internalValue, setInternalValue] = useState(company.address);
const [updateCompany] = useUpdateOneCompanyMutation();
useEffect(() => {
setInternalValue(company.address);
}, [company.address]);
async function handleChange(newValue: string) {
setInternalValue(newValue);
}
async function handleSubmit() {
await updateCompany({
variables: {
where: {
id: company.id,
},
data: {
address: internalValue ?? '',
},
},
});
}
async function handleCancel() {
setInternalValue(company.address);
}
return (
<RecoilScope SpecificContext={FieldContext}>
<EditableField
onSubmit={handleSubmit}
onCancel={handleCancel}
iconLabel={<IconMap />}
editModeContent={
<TextInputEdit
placeholder={'Address'}
autoFocus
value={internalValue}
onChange={(newValue: string) => {
handleChange(newValue);
}}
/>
}
displayModeContent={internalValue ?? ''}
isDisplayModeContentEmpty={!(internalValue !== '')}
/>
</RecoilScope>
);
}

View File

@ -1,79 +0,0 @@
import { useEffect, useState } from 'react';
import { EditableField } from '@/ui/editable-field/components/EditableField';
import { FieldContext } from '@/ui/editable-field/states/FieldContext';
import { EditableFieldEditModeDate } from '@/ui/editable-field/variants/components/EditableFieldEditModeDate';
import { IconCalendar } from '@/ui/icon';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { Company, useUpdateOneCompanyMutation } from '~/generated/graphql';
import { formatToHumanReadableDate } from '~/utils';
import { parseDate } from '~/utils/date-utils';
type OwnProps = {
company: Pick<Company, 'id' | 'createdAt'>;
};
export function CompanyCreatedAtEditableField({ company }: OwnProps) {
const [internalValue, setInternalValue] = useState(company.createdAt);
const [updateCompany] = useUpdateOneCompanyMutation();
useEffect(() => {
setInternalValue(company.createdAt);
}, [company.createdAt]);
// TODO: refactor change and submit
async function handleChange(newValue: string) {
setInternalValue(newValue);
await updateCompany({
variables: {
where: {
id: company.id,
},
data: {
createdAt: newValue ?? '',
},
},
});
}
async function handleSubmit() {
await updateCompany({
variables: {
where: {
id: company.id,
},
data: {
createdAt: internalValue ?? '',
},
},
});
}
async function handleCancel() {
setInternalValue(company.createdAt);
}
return (
<RecoilScope SpecificContext={FieldContext}>
<EditableField
onSubmit={handleSubmit}
onCancel={handleCancel}
iconLabel={<IconCalendar />}
editModeContent={
<EditableFieldEditModeDate
value={internalValue}
onChange={handleChange}
/>
}
displayModeContent={
internalValue !== ''
? formatToHumanReadableDate(parseDate(internalValue).toJSDate())
: 'No date'
}
isDisplayModeContentEmpty={!(internalValue !== '')}
/>
</RecoilScope>
);
}

View File

@ -1,67 +0,0 @@
import { useEffect, useState } from 'react';
import { EditableField } from '@/ui/editable-field/components/EditableField';
import { FieldDisplayURL } from '@/ui/editable-field/components/FieldDisplayURL';
import { FieldContext } from '@/ui/editable-field/states/FieldContext';
import { IconLink } from '@/ui/icon';
import { TextInputEdit } from '@/ui/input/text/components/TextInputEdit';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { Company, useUpdateOneCompanyMutation } from '~/generated/graphql';
type OwnProps = {
company: Pick<Company, 'id' | 'domainName'>;
};
export function CompanyDomainNameEditableField({ company }: OwnProps) {
const [internalValue, setInternalValue] = useState(company.domainName);
const [updateCompany] = useUpdateOneCompanyMutation();
useEffect(() => {
setInternalValue(company.domainName);
}, [company.domainName]);
async function handleChange(newValue: string) {
setInternalValue(newValue);
}
async function handleSubmit() {
await updateCompany({
variables: {
where: {
id: company.id,
},
data: {
domainName: internalValue ?? '',
},
},
});
}
async function handleCancel() {
setInternalValue(company.domainName);
}
return (
<RecoilScope SpecificContext={FieldContext}>
<EditableField
iconLabel={<IconLink />}
onCancel={handleCancel}
onSubmit={handleSubmit}
editModeContent={
<TextInputEdit
placeholder={'URL'}
autoFocus
value={internalValue}
onChange={(newValue: string) => {
handleChange(newValue);
}}
/>
}
displayModeContent={<FieldDisplayURL URL={internalValue} />}
useEditButton
isDisplayModeContentEmpty={!(internalValue !== '')}
/>
</RecoilScope>
);
}

View File

@ -1,80 +0,0 @@
import { useEffect, useState } from 'react';
import { EditableField } from '@/ui/editable-field/components/EditableField';
import { FieldContext } from '@/ui/editable-field/states/FieldContext';
import { IconUsers } from '@/ui/icon';
import { TextInputEdit } from '@/ui/input/text/components/TextInputEdit';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { Company, useUpdateOneCompanyMutation } from '~/generated/graphql';
import {
canBeCastAsIntegerOrNull,
castAsIntegerOrNull,
} from '~/utils/cast-as-integer-or-null';
type OwnProps = {
company: Pick<Company, 'id' | 'employees'>;
};
export function CompanyEmployeesEditableField({ company }: OwnProps) {
const [internalValue, setInternalValue] = useState(
company.employees?.toString(),
);
const [updateCompany] = useUpdateOneCompanyMutation();
useEffect(() => {
setInternalValue(company.employees?.toString());
}, [company.employees]);
async function handleChange(newValue: string) {
setInternalValue(newValue);
}
async function handleSubmit() {
if (!canBeCastAsIntegerOrNull(internalValue)) {
handleCancel();
return;
}
const valueCastedAsNumberOrNull = castAsIntegerOrNull(internalValue);
await updateCompany({
variables: {
where: {
id: company.id,
},
data: {
employees: valueCastedAsNumberOrNull,
},
},
});
setInternalValue(valueCastedAsNumberOrNull?.toString());
}
async function handleCancel() {
setInternalValue(company.employees?.toString());
}
return (
<RecoilScope SpecificContext={FieldContext}>
<EditableField
onSubmit={handleSubmit}
onCancel={handleCancel}
iconLabel={<IconUsers />}
editModeContent={
<TextInputEdit
placeholder={'Employees'}
autoFocus
value={internalValue ?? ''}
onChange={(newValue: string) => {
handleChange(newValue);
}}
/>
}
displayModeContent={internalValue}
isDisplayModeContentEmpty={!(internalValue && internalValue !== '0')}
/>
</RecoilScope>
);
}

View File

@ -2,6 +2,7 @@ import { getOperationName } from '@apollo/client/utilities';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import { useOpenCreateActivityDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds'; import { useOpenCreateActivityDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds';
import { ActivityTargetableEntityType } from '@/activities/types/ActivityTargetableEntity';
import { GET_PIPELINES } from '@/pipeline/queries'; import { GET_PIPELINES } from '@/pipeline/queries';
import { ActionBarEntry } from '@/ui/action-bar/components/ActionBarEntry'; import { ActionBarEntry } from '@/ui/action-bar/components/ActionBarEntry';
import { IconCheckbox, IconNotes, IconTrash } from '@/ui/icon'; import { IconCheckbox, IconNotes, IconTrash } from '@/ui/icon';
@ -11,7 +12,6 @@ import { selectedRowIdsSelector } from '@/ui/table/states/selectedRowIdsSelector
import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState'; import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState';
import { import {
ActivityType, ActivityType,
CommentableType,
useDeleteManyCompaniesMutation, useDeleteManyCompaniesMutation,
} from '~/generated/graphql'; } from '~/generated/graphql';
@ -22,7 +22,7 @@ export function useOpenActionBar() {
useOpenCreateActivityDrawerForSelectedRowIds(); useOpenCreateActivityDrawerForSelectedRowIds();
async function handleActivityClick(type: ActivityType) { async function handleActivityClick(type: ActivityType) {
openCreateActivityRightDrawer(type, CommentableType.Company); openCreateActivityRightDrawer(type, ActivityTargetableEntityType.Company);
} }
const selectedRowIds = useRecoilValue(selectedRowIdsSelector); const selectedRowIds = useRecoilValue(selectedRowIdsSelector);

View File

@ -3,6 +3,7 @@ import { IconCheckbox, IconNotes, IconTrash } from '@tabler/icons-react';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import { useOpenCreateActivityDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds'; import { useOpenCreateActivityDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds';
import { ActivityTargetableEntityType } from '@/activities/types/ActivityTargetableEntity';
import { GET_PIPELINES } from '@/pipeline/queries'; import { GET_PIPELINES } from '@/pipeline/queries';
import { ContextMenuEntry } from '@/ui/context-menu/components/ContextMenuEntry'; import { ContextMenuEntry } from '@/ui/context-menu/components/ContextMenuEntry';
import { useResetTableRowSelection } from '@/ui/table/hooks/useResetTableRowSelection'; import { useResetTableRowSelection } from '@/ui/table/hooks/useResetTableRowSelection';
@ -11,7 +12,6 @@ import { selectedRowIdsSelector } from '@/ui/table/states/selectedRowIdsSelector
import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState'; import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState';
import { import {
ActivityType, ActivityType,
CommentableType,
useDeleteManyCompaniesMutation, useDeleteManyCompaniesMutation,
} from '~/generated/graphql'; } from '~/generated/graphql';
@ -22,7 +22,7 @@ export function useOpenContextMenu() {
useOpenCreateActivityDrawerForSelectedRowIds(); useOpenCreateActivityDrawerForSelectedRowIds();
async function handleButtonClick(type: ActivityType) { async function handleButtonClick(type: ActivityType) {
openCreateActivityRightDrawer(type, CommentableType.Company); openCreateActivityRightDrawer(type, ActivityTargetableEntityType.Company);
} }
const selectedRowIds = useRecoilValue(selectedRowIdsSelector); const selectedRowIds = useRecoilValue(selectedRowIdsSelector);

View File

@ -1,10 +1,10 @@
import { gql } from '@apollo/client'; import { gql } from '@apollo/client';
import { CommentableEntityForSelect } from '@/activities/types/CommentableEntityForSelect'; import { ActivityTargetableEntityType } from '@/activities/types/ActivityTargetableEntity';
import { ActivityTargetableEntityForSelect } from '@/activities/types/ActivityTargetableEntityForSelect';
import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery'; import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery';
import { SelectedSortType } from '@/ui/filter-n-sort/types/interface'; import { SelectedSortType } from '@/ui/filter-n-sort/types/interface';
import { import {
CommentableType,
CompanyOrderByWithRelationInput as Companies_Order_By, CompanyOrderByWithRelationInput as Companies_Order_By,
CompanyWhereInput as Companies_Bool_Exp, CompanyWhereInput as Companies_Bool_Exp,
SortOrder as Order_By, SortOrder as Order_By,
@ -65,11 +65,11 @@ export function useFilteredSearchCompanyQuery({
mappingFunction: (company) => mappingFunction: (company) =>
({ ({
id: company.id, id: company.id,
entityType: CommentableType.Company, entityType: ActivityTargetableEntityType.Company,
name: company.name, name: company.name,
avatarUrl: getLogoUrlFromDomainName(company.domainName), avatarUrl: getLogoUrlFromDomainName(company.domainName),
avatarType: 'squared', avatarType: 'squared',
} as CommentableEntityForSelect), } as ActivityTargetableEntityForSelect),
searchFilter, searchFilter,
limit, limit,
}); });

View File

@ -1,5 +1,7 @@
import { gql } from '@apollo/client'; import { gql } from '@apollo/client';
import { useSetRecoilState } from 'recoil';
import { genericEntitiesFamilyState } from '@/ui/editable-field/states/genericEntitiesFamilyState';
import { useGetCompanyQuery } from '~/generated/graphql'; import { useGetCompanyQuery } from '~/generated/graphql';
export const GET_COMPANY = gql` export const GET_COMPANY = gql`
@ -33,5 +35,13 @@ export const GET_COMPANY = gql`
`; `;
export function useCompanyQuery(id: string) { export function useCompanyQuery(id: string) {
return useGetCompanyQuery({ variables: { where: { id } } }); const updateCompanyShowPage = useSetRecoilState(
genericEntitiesFamilyState(id),
);
return useGetCompanyQuery({
variables: { where: { id } },
onCompleted: (data) => {
updateCompanyShowPage(data?.findUniqueCompany);
},
});
} }

View File

@ -1,43 +1,38 @@
import { gql } from '@apollo/client'; import { gql } from '@apollo/client';
export const COMPANY_FIELDS_FRAGMENT = gql`
fragment CompanyFieldsFragment on Company {
accountOwner {
id
email
displayName
avatarUrl
}
address
createdAt
domainName
employees
linkedinUrl
id
name
}
`;
export const UPDATE_ONE_COMPANY = gql` export const UPDATE_ONE_COMPANY = gql`
mutation UpdateOneCompany( mutation UpdateOneCompany(
$where: CompanyWhereUniqueInput! $where: CompanyWhereUniqueInput!
$data: CompanyUpdateInput! $data: CompanyUpdateInput!
) { ) {
updateOneCompany(data: $data, where: $where) { updateOneCompany(data: $data, where: $where) {
accountOwner { ...CompanyFieldsFragment
id
email
displayName
firstName
lastName
}
address
createdAt
domainName
employees
linkedinUrl
id
name
} }
} }
`; `;
export const INSERT_COMPANY_FRAGMENT = gql`
fragment InsertCompanyFragment on Company {
domainName
address
id
name
createdAt
}
`;
export const INSERT_ONE_COMPANY = gql` export const INSERT_ONE_COMPANY = gql`
mutation InsertOneCompany($data: CompanyCreateInput!) { mutation InsertOneCompany($data: CompanyCreateInput!) {
createOneCompany(data: $data) { createOneCompany(data: $data) {
...InsertCompanyFragment ...CompanyFieldsFragment
} }
} }
`; `;

View File

@ -8,11 +8,14 @@ import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIn
import { IconList } from '@/ui/icon'; import { IconList } from '@/ui/icon';
import { EntityTable } from '@/ui/table/components/EntityTable'; import { EntityTable } from '@/ui/table/components/EntityTable';
import { GenericEntityTableData } from '@/ui/table/components/GenericEntityTableData'; import { GenericEntityTableData } from '@/ui/table/components/GenericEntityTableData';
import { useUpsertEntityTableItem } from '@/ui/table/hooks/useUpsertEntityTableItem';
import { TableContext } from '@/ui/table/states/TableContext'; import { TableContext } from '@/ui/table/states/TableContext';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { useTableViewFields } from '@/views/hooks/useTableViewFields';
import { useViewSorts } from '@/views/hooks/useViewSorts'; import { useViewSorts } from '@/views/hooks/useViewSorts';
import { currentViewIdState } from '@/views/states/currentViewIdState'; import { currentViewIdState } from '@/views/states/currentViewIdState';
import { import {
UpdateOneCompanyMutationVariables,
useGetCompaniesQuery, useGetCompaniesQuery,
useUpdateOneCompanyMutation, useUpdateOneCompanyMutation,
} from '~/generated/graphql'; } from '~/generated/graphql';
@ -24,6 +27,13 @@ import { defaultOrderBy } from '../../queries';
export function CompanyTable() { export function CompanyTable() {
const currentViewId = useRecoilValue(currentViewIdState); const currentViewId = useRecoilValue(currentViewIdState);
const orderBy = useRecoilScopedValue(sortsOrderByScopedState, TableContext); const orderBy = useRecoilScopedValue(sortsOrderByScopedState, TableContext);
const [updateEntityMutation] = useUpdateOneCompanyMutation();
const upsertEntityTableItem = useUpsertEntityTableItem();
const { handleColumnsChange } = useTableViewFields({
objectName: 'company',
viewFieldDefinitions: companyViewFields,
});
const { updateSorts } = useViewSorts({ const { updateSorts } = useViewSorts({
availableSorts, availableSorts,
Context: TableContext, Context: TableContext,
@ -38,20 +48,33 @@ export function CompanyTable() {
return ( return (
<> <>
<GenericEntityTableData <GenericEntityTableData
objectName="company"
getRequestResultKey="companies" getRequestResultKey="companies"
useGetRequest={useGetCompaniesQuery} useGetRequest={useGetCompaniesQuery}
orderBy={orderBy.length ? orderBy : defaultOrderBy} orderBy={orderBy.length ? orderBy : defaultOrderBy}
whereFilters={whereFilters} whereFilters={whereFilters}
viewFieldDefinitions={companyViewFields}
filterDefinitionArray={companiesFilters} filterDefinitionArray={companiesFilters}
/> />
<EntityTable <EntityTable
viewName="All Companies" viewName="All Companies"
viewIcon={<IconList size={16} />} viewIcon={<IconList size={16} />}
availableSorts={availableSorts} availableSorts={availableSorts}
onColumnsChange={handleColumnsChange}
onSortsUpdate={currentViewId ? updateSorts : undefined} onSortsUpdate={currentViewId ? updateSorts : undefined}
useUpdateEntityMutation={useUpdateOneCompanyMutation} updateEntityMutation={({
variables,
}: {
variables: UpdateOneCompanyMutationVariables;
}) =>
updateEntityMutation({
variables,
onCompleted: (data) => {
if (!data.updateOneCompany) {
return;
}
upsertEntityTableItem(data.updateOneCompany);
},
})
}
/> />
</> </>
); );

View File

@ -2,32 +2,21 @@ import { useEffect } from 'react';
import { useSetRecoilState } from 'recoil'; import { useSetRecoilState } from 'recoil';
import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData'; import { useSetEntityTableData } from '@/ui/table/hooks/useSetEntityTableData';
import { entityTableDimensionsState } from '@/ui/table/states/entityTableDimensionsState'; import { tableColumnsState } from '@/ui/table/states/tableColumnsState';
import { viewFieldsState } from '@/ui/table/states/viewFieldsState';
import { companyViewFields } from '../../constants/companyViewFields'; import { companyViewFields } from '../../constants/companyViewFields';
import { mockedCompaniesData } from './companies-mock-data'; import { mockedCompaniesData } from './companies-mock-data';
export function CompanyTableMockData() { export function CompanyTableMockData() {
const setEntityTableDimensions = useSetRecoilState( const setColumns = useSetRecoilState(tableColumnsState);
entityTableDimensionsState,
);
const setViewFieldsState = useSetRecoilState(viewFieldsState);
const setEntityTableData = useSetEntityTableData(); const setEntityTableData = useSetEntityTableData();
setEntityTableData(mockedCompaniesData, []); setEntityTableData(mockedCompaniesData, []);
useEffect(() => { useEffect(() => {
setViewFieldsState({ setColumns(companyViewFields);
objectName: 'company', }, [setColumns]);
viewFields: companyViewFields,
});
setEntityTableDimensions((prevState) => ({
...prevState,
numberOfColumns: companyViewFields.length,
}));
}, [setEntityTableDimensions, setViewFieldsState]);
return <></>; return <></>;
} }

View File

@ -13,7 +13,7 @@ export function CompanyTableMockMode() {
viewName="All Companies" viewName="All Companies"
viewIcon={<IconList size={16} />} viewIcon={<IconList size={16} />}
availableSorts={availableSorts} availableSorts={availableSorts}
useUpdateEntityMutation={useUpdateOneCompanyMutation} updateEntityMutation={[useUpdateOneCompanyMutation()]}
/> />
</> </>
); );

View File

@ -1,73 +0,0 @@
import { useFilteredSearchCompanyQuery } from '@/companies/queries';
import { SingleEntitySelect } from '@/ui/input/relation-picker/components/SingleEntitySelect';
import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState';
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { useEditableCell } from '@/ui/table/editable-cell/hooks/useEditableCell';
import { isCreateModeScopedState } from '@/ui/table/editable-cell/states/isCreateModeScopedState';
import { TableHotkeyScope } from '@/ui/table/types/TableHotkeyScope';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import {
Company,
Person,
useUpdateOnePersonMutation,
} from '~/generated/graphql';
export type OwnProps = {
people: Pick<Person, 'id'> & { company?: Pick<Company, 'id'> | null };
};
export function PeopleCompanyPicker({ people }: OwnProps) {
const [, setIsCreating] = useRecoilScopedState(isCreateModeScopedState);
const [searchFilter] = useRecoilScopedState(
relationPickerSearchFilterScopedState,
);
const [updatePerson] = useUpdateOnePersonMutation();
const { closeEditableCell } = useEditableCell();
const addToScopeStack = useSetHotkeyScope();
const companies = useFilteredSearchCompanyQuery({
searchFilter,
selectedIds: people.company?.id ? [people.company.id] : [],
});
async function handleEntitySelected(
entity: EntityForSelect | null | undefined,
) {
if (entity) {
await updatePerson({
variables: {
where: {
id: people.id,
},
data: {
company: { connect: { id: entity.id } },
},
},
});
}
closeEditableCell();
}
function handleCreate() {
setIsCreating(true);
addToScopeStack(TableHotkeyScope.CellDoubleTextInput);
}
return (
<SingleEntitySelect
onCreate={handleCreate}
onCancel={() => closeEditableCell()}
onEntitySelected={handleEntitySelected}
entities={{
entitiesToSelect: companies.entitiesToSelect,
selectedEntity: companies.selectedEntities[0],
loading: companies.loading,
}}
/>
);
}

View File

@ -1,104 +0,0 @@
import {
IconCalendar,
IconMail,
IconMap,
IconPhone,
} from '@tabler/icons-react';
import { PropertyBox } from '@/ui/editable-field/property-box/components/PropertyBox';
import { DateEditableField } from '@/ui/editable-field/variants/components/DateEditableField';
import { PhoneEditableField } from '@/ui/editable-field/variants/components/PhoneEditableField';
import { TextEditableField } from '@/ui/editable-field/variants/components/TextEditableField';
import {
Company,
Person,
useUpdateOnePersonMutation,
} from '~/generated/graphql';
import { PeopleCompanyEditableField } from '../editable-field/components/PeopleCompanyEditableField';
type OwnProps = {
person: Pick<
Person,
'id' | 'city' | 'email' | 'displayName' | 'phone' | 'createdAt'
> & {
company?: Pick<Company, 'id' | 'name' | 'domainName'> | null;
};
};
export function PersonPropertyBox({ person }: OwnProps) {
const [updatePerson] = useUpdateOnePersonMutation();
return (
<PropertyBox extraPadding={true}>
<TextEditableField
value={person.email}
icon={<IconMail />}
placeholder={'Email'}
onSubmit={(newEmail) => {
updatePerson({
variables: {
where: {
id: person.id,
},
data: {
email: newEmail,
},
},
});
}}
/>
<PhoneEditableField
value={person.phone}
icon={<IconPhone />}
placeholder={'Phone'}
onSubmit={(newPhone) => {
updatePerson({
variables: {
where: {
id: person.id,
},
data: {
phone: newPhone,
},
},
});
}}
/>
<DateEditableField
value={person.createdAt}
icon={<IconCalendar />}
onSubmit={(newDate) => {
updatePerson({
variables: {
where: {
id: person.id,
},
data: {
createdAt: newDate,
},
},
});
}}
/>
<PeopleCompanyEditableField people={person} />
<TextEditableField
value={person.city}
icon={<IconMap />}
placeholder={'City'}
onSubmit={(newCity) => {
updatePerson({
variables: {
where: {
id: person.id,
},
data: {
city: newCity,
},
},
});
}}
/>
</PropertyBox>
);
}

View File

@ -1,22 +0,0 @@
import { BrowserRouter } from 'react-router-dom';
import type { Meta, StoryObj } from '@storybook/react';
import { mockedPeopleData } from '~/testing/mock-data/people';
import { PeopleCompanyEditableField } from '../../editable-field/components/PeopleCompanyEditableField';
const meta: Meta<typeof PeopleCompanyEditableField> = {
title: 'Modules/People/EditableFields/PeopleCompanyEditableField',
component: PeopleCompanyEditableField,
};
export default meta;
type Story = StoryObj<typeof PeopleCompanyEditableField>;
export const Default: Story = {
render: () => (
<BrowserRouter>
<PeopleCompanyEditableField people={mockedPeopleData[0]} />
</BrowserRouter>
),
};

View File

@ -1,49 +0,0 @@
import { IconBuildingSkyscraper } from '@tabler/icons-react';
import { CompanyChip } from '@/companies/components/CompanyChip';
import { EditableField } from '@/ui/editable-field/components/EditableField';
import { FieldContext } from '@/ui/editable-field/states/FieldContext';
import { RelationPickerHotkeyScope } from '@/ui/input/relation-picker/types/RelationPickerHotkeyScope';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { Company, Person } from '~/generated/graphql';
import { getLogoUrlFromDomainName } from '~/utils';
import { PeopleCompanyEditableFieldEditMode } from './PeopleCompanyEditableFieldEditMode';
export type OwnProps = {
people: Pick<Person, 'id'> & {
company?: Pick<Company, 'id' | 'name' | 'domainName'> | null;
};
};
export function PeopleCompanyEditableField({ people }: OwnProps) {
return (
<RecoilScope SpecificContext={FieldContext}>
<RecoilScope>
<EditableField
useEditButton
customEditHotkeyScope={{
scope: RelationPickerHotkeyScope.RelationPicker,
}}
iconLabel={<IconBuildingSkyscraper />}
editModeContent={
<PeopleCompanyEditableFieldEditMode people={people} />
}
displayModeContent={
people.company ? (
<CompanyChip
id={people.company.id}
name={people.company.name}
pictureUrl={getLogoUrlFromDomainName(people.company.domainName)}
/>
) : (
<></>
)
}
isDisplayModeContentEmpty={!people.company}
isDisplayModeFixHeight
/>
</RecoilScope>
</RecoilScope>
);
}

View File

@ -1,74 +0,0 @@
import styled from '@emotion/styled';
import { useFilteredSearchCompanyQuery } from '@/companies/queries';
import { useEditableField } from '@/ui/editable-field/hooks/useEditableField';
import { SingleEntitySelect } from '@/ui/input/relation-picker/components/SingleEntitySelect';
import { relationPickerSearchFilterScopedState } from '@/ui/input/relation-picker/states/relationPickerSearchFilterScopedState';
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import {
Company,
Person,
useUpdateOnePersonMutation,
} from '~/generated/graphql';
export type OwnProps = {
people: Pick<Person, 'id'> & { company?: Pick<Company, 'id'> | null };
};
const StyledContainer = styled.div`
left: 0px;
position: absolute;
top: -8px;
`;
export function PeopleCompanyEditableFieldEditMode({ people }: OwnProps) {
const { closeEditableField } = useEditableField();
const [searchFilter] = useRecoilScopedState(
relationPickerSearchFilterScopedState,
);
const [updatePerson] = useUpdateOnePersonMutation();
const companies = useFilteredSearchCompanyQuery({
searchFilter,
selectedIds: people.company?.id ? [people.company.id] : [],
});
async function handleEntitySelected(
entity: EntityForSelect | null | undefined,
) {
if (entity) {
await updatePerson({
variables: {
where: {
id: people.id,
},
data: {
company: { connect: { id: entity.id } },
},
},
});
}
closeEditableField();
}
function handleCancel() {
closeEditableField();
}
return (
<StyledContainer>
<SingleEntitySelect
onEntitySelected={handleEntitySelected}
entities={{
entitiesToSelect: companies.entitiesToSelect,
selectedEntity: companies.selectedEntities[0],
loading: companies.loading,
}}
onCancel={handleCancel}
/>
</StyledContainer>
);
}

View File

@ -2,17 +2,14 @@ import { getOperationName } from '@apollo/client/utilities';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import { useOpenCreateActivityDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds'; import { useOpenCreateActivityDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds';
import { ActivityTargetableEntityType } from '@/activities/types/ActivityTargetableEntity';
import { ActionBarEntry } from '@/ui/action-bar/components/ActionBarEntry'; import { ActionBarEntry } from '@/ui/action-bar/components/ActionBarEntry';
import { IconCheckbox, IconNotes, IconTrash } from '@/ui/icon'; import { IconCheckbox, IconNotes, IconTrash } from '@/ui/icon';
import { useResetTableRowSelection } from '@/ui/table/hooks/useResetTableRowSelection'; import { useResetTableRowSelection } from '@/ui/table/hooks/useResetTableRowSelection';
import { actionBarEntriesState } from '@/ui/table/states/ActionBarEntriesState'; import { actionBarEntriesState } from '@/ui/table/states/ActionBarEntriesState';
import { selectedRowIdsSelector } from '@/ui/table/states/selectedRowIdsSelector'; import { selectedRowIdsSelector } from '@/ui/table/states/selectedRowIdsSelector';
import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState'; import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState';
import { import { ActivityType, useDeleteManyPersonMutation } from '~/generated/graphql';
ActivityType,
CommentableType,
useDeleteManyPersonMutation,
} from '~/generated/graphql';
import { GET_PEOPLE } from '../queries'; import { GET_PEOPLE } from '../queries';
@ -23,7 +20,7 @@ export function useOpenActionBar() {
useOpenCreateActivityDrawerForSelectedRowIds(); useOpenCreateActivityDrawerForSelectedRowIds();
async function handleActivityClick(type: ActivityType) { async function handleActivityClick(type: ActivityType) {
openCreateActivityRightDrawer(type, CommentableType.Person); openCreateActivityRightDrawer(type, ActivityTargetableEntityType.Person);
} }
const selectedRowIds = useRecoilValue(selectedRowIdsSelector); const selectedRowIds = useRecoilValue(selectedRowIdsSelector);

View File

@ -3,16 +3,13 @@ import { IconCheckbox, IconNotes, IconTrash } from '@tabler/icons-react';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'; import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import { useOpenCreateActivityDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds'; import { useOpenCreateActivityDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds';
import { ActivityTargetableEntityType } from '@/activities/types/ActivityTargetableEntity';
import { ContextMenuEntry } from '@/ui/context-menu/components/ContextMenuEntry'; import { ContextMenuEntry } from '@/ui/context-menu/components/ContextMenuEntry';
import { useResetTableRowSelection } from '@/ui/table/hooks/useResetTableRowSelection'; import { useResetTableRowSelection } from '@/ui/table/hooks/useResetTableRowSelection';
import { contextMenuEntriesState } from '@/ui/table/states/ContextMenuEntriesState'; import { contextMenuEntriesState } from '@/ui/table/states/ContextMenuEntriesState';
import { selectedRowIdsSelector } from '@/ui/table/states/selectedRowIdsSelector'; import { selectedRowIdsSelector } from '@/ui/table/states/selectedRowIdsSelector';
import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState'; import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState';
import { import { ActivityType, useDeleteManyPersonMutation } from '~/generated/graphql';
ActivityType,
CommentableType,
useDeleteManyPersonMutation,
} from '~/generated/graphql';
import { GET_PEOPLE } from '../queries'; import { GET_PEOPLE } from '../queries';
@ -23,7 +20,7 @@ export function useOpenContextMenu() {
useOpenCreateActivityDrawerForSelectedRowIds(); useOpenCreateActivityDrawerForSelectedRowIds();
async function handleActivityClick(type: ActivityType) { async function handleActivityClick(type: ActivityType) {
openCreateActivityRightDrawer(type, CommentableType.Person); openCreateActivityRightDrawer(type, ActivityTargetableEntityType.Person);
} }
const selectedRowIds = useRecoilValue(selectedRowIdsSelector); const selectedRowIds = useRecoilValue(selectedRowIdsSelector);

View File

@ -1,17 +1,17 @@
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { availableFiltersScopedState } from '@/ui/filter-n-sort/states/availableFiltersScopedState';
import { useResetTableRowSelection } from '@/ui/table/hooks/useResetTableRowSelection';
import { isFetchingEntityTableDataState } from '@/ui/table/states/isFetchingEntityTableDataState';
import { numberOfTableRowsState } from '@/ui/table/states/numberOfTableRowsState';
import { TableContext } from '@/ui/table/states/TableContext';
import { tableRowIdsState } from '@/ui/table/states/tableRowIdsState';
import { currentPageLocationState } from '@/ui/utilities/loading-state/states/currentPageLocationState'; import { currentPageLocationState } from '@/ui/utilities/loading-state/states/currentPageLocationState';
import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId'; import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
import { GetPeopleQuery } from '~/generated/graphql'; import { GetPeopleQuery } from '~/generated/graphql';
import { peopleFilters } from '~/pages/people/people-filters';
import { peopleFilters } from '../../../pages/people/people-filters';
import { availableFiltersScopedState } from '../../ui/filter-n-sort/states/availableFiltersScopedState';
import { useResetTableRowSelection } from '../../ui/table/hooks/useResetTableRowSelection';
import { entityTableDimensionsState } from '../../ui/table/states/entityTableDimensionsState';
import { isFetchingEntityTableDataState } from '../../ui/table/states/isFetchingEntityTableDataState';
import { TableContext } from '../../ui/table/states/TableContext';
import { tableRowIdsState } from '../../ui/table/states/tableRowIdsState';
import { peopleCityFamilyState } from '../states/peopleCityFamilyState'; import { peopleCityFamilyState } from '../states/peopleCityFamilyState';
import { peopleCompanyFamilyState } from '../states/peopleCompanyFamilyState'; import { peopleCompanyFamilyState } from '../states/peopleCompanyFamilyState';
import { peopleCreatedAtFamilyState } from '../states/peopleCreatedAtFamilyState'; import { peopleCreatedAtFamilyState } from '../states/peopleCreatedAtFamilyState';
@ -124,10 +124,7 @@ export function useSetPeopleEntityTable() {
resetTableRowSelection(); resetTableRowSelection();
set(entityTableDimensionsState, { set(numberOfTableRowsState, peopleIds.length);
numberOfColumns: 10,
numberOfRows: peopleIds.length,
});
set(availableFiltersScopedState(tableContextScopeId), peopleFilters); set(availableFiltersScopedState(tableContextScopeId), peopleFilters);

View File

@ -1,10 +1,10 @@
import { gql } from '@apollo/client'; import { gql } from '@apollo/client';
import { CommentableEntityForSelect } from '@/activities/types/CommentableEntityForSelect'; import { ActivityTargetableEntityType } from '@/activities/types/ActivityTargetableEntity';
import { ActivityTargetableEntityForSelect } from '@/activities/types/ActivityTargetableEntityForSelect';
import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery'; import { useFilteredSearchEntityQuery } from '@/search/hooks/useFilteredSearchEntityQuery';
import { SelectedSortType } from '@/ui/filter-n-sort/types/interface'; import { SelectedSortType } from '@/ui/filter-n-sort/types/interface';
import { import {
CommentableType,
PersonOrderByWithRelationInput as People_Order_By, PersonOrderByWithRelationInput as People_Order_By,
PersonWhereInput as People_Bool_Exp, PersonWhereInput as People_Bool_Exp,
SortOrder, SortOrder,
@ -69,11 +69,11 @@ export function useFilteredSearchPeopleQuery({
mappingFunction: (entity) => mappingFunction: (entity) =>
({ ({
id: entity.id, id: entity.id,
entityType: CommentableType.Person, entityType: ActivityTargetableEntityType.Person,
name: `${entity.firstName} ${entity.lastName}`, name: `${entity.firstName} ${entity.lastName}`,
avatarUrl: entity.avatarUrl, avatarUrl: entity.avatarUrl,
avatarType: 'rounded', avatarType: 'rounded',
} as CommentableEntityForSelect), } as ActivityTargetableEntityForSelect),
searchFilter, searchFilter,
limit, limit,
}); });

View File

@ -1,5 +1,7 @@
import { gql } from '@apollo/client'; import { gql } from '@apollo/client';
import { useSetRecoilState } from 'recoil';
import { genericEntitiesFamilyState } from '@/ui/editable-field/states/genericEntitiesFamilyState';
import { useGetPersonQuery } from '~/generated/graphql'; import { useGetPersonQuery } from '~/generated/graphql';
export const GET_PERSON = gql` export const GET_PERSON = gql`
@ -37,5 +39,13 @@ export const GET_PERSON = gql`
`; `;
export function usePersonQuery(id: string) { export function usePersonQuery(id: string) {
return useGetPersonQuery({ variables: { id } }); const updatePersonShowPage = useSetRecoilState(
genericEntitiesFamilyState(id),
);
return useGetPersonQuery({
variables: { id },
onCompleted: (data) => {
updatePersonShowPage(data?.findUniquePerson);
},
});
} }

View File

@ -8,11 +8,14 @@ import { turnFilterIntoWhereClause } from '@/ui/filter-n-sort/utils/turnFilterIn
import { IconList } from '@/ui/icon'; import { IconList } from '@/ui/icon';
import { EntityTable } from '@/ui/table/components/EntityTable'; import { EntityTable } from '@/ui/table/components/EntityTable';
import { GenericEntityTableData } from '@/ui/table/components/GenericEntityTableData'; import { GenericEntityTableData } from '@/ui/table/components/GenericEntityTableData';
import { useUpsertEntityTableItem } from '@/ui/table/hooks/useUpsertEntityTableItem';
import { TableContext } from '@/ui/table/states/TableContext'; import { TableContext } from '@/ui/table/states/TableContext';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue'; import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { useTableViewFields } from '@/views/hooks/useTableViewFields';
import { useViewSorts } from '@/views/hooks/useViewSorts'; import { useViewSorts } from '@/views/hooks/useViewSorts';
import { currentViewIdState } from '@/views/states/currentViewIdState'; import { currentViewIdState } from '@/views/states/currentViewIdState';
import { import {
UpdateOnePersonMutationVariables,
useGetPeopleQuery, useGetPeopleQuery,
useUpdateOnePersonMutation, useUpdateOnePersonMutation,
} from '~/generated/graphql'; } from '~/generated/graphql';
@ -24,6 +27,13 @@ import { defaultOrderBy } from '../../queries';
export function PeopleTable() { export function PeopleTable() {
const currentViewId = useRecoilValue(currentViewIdState); const currentViewId = useRecoilValue(currentViewIdState);
const orderBy = useRecoilScopedValue(sortsOrderByScopedState, TableContext); const orderBy = useRecoilScopedValue(sortsOrderByScopedState, TableContext);
const [updateEntityMutation] = useUpdateOnePersonMutation();
const upsertEntityTableItem = useUpsertEntityTableItem();
const { handleColumnsChange } = useTableViewFields({
objectName: 'person',
viewFieldDefinitions: peopleViewFields,
});
const { updateSorts } = useViewSorts({ const { updateSorts } = useViewSorts({
availableSorts, availableSorts,
Context: TableContext, Context: TableContext,
@ -38,20 +48,33 @@ export function PeopleTable() {
return ( return (
<> <>
<GenericEntityTableData <GenericEntityTableData
objectName="person"
getRequestResultKey="people" getRequestResultKey="people"
useGetRequest={useGetPeopleQuery} useGetRequest={useGetPeopleQuery}
orderBy={orderBy.length ? orderBy : defaultOrderBy} orderBy={orderBy.length ? orderBy : defaultOrderBy}
whereFilters={whereFilters} whereFilters={whereFilters}
viewFieldDefinitions={peopleViewFields}
filterDefinitionArray={peopleFilters} filterDefinitionArray={peopleFilters}
/> />
<EntityTable <EntityTable
viewName="All People" viewName="All People"
viewIcon={<IconList size={16} />} viewIcon={<IconList size={16} />}
availableSorts={availableSorts} availableSorts={availableSorts}
onColumnsChange={handleColumnsChange}
onSortsUpdate={currentViewId ? updateSorts : undefined} onSortsUpdate={currentViewId ? updateSorts : undefined}
useUpdateEntityMutation={useUpdateOnePersonMutation} updateEntityMutation={({
variables,
}: {
variables: UpdateOnePersonMutationVariables;
}) =>
updateEntityMutation({
variables,
onCompleted: (data) => {
if (!data.updateOnePerson) {
return;
}
upsertEntityTableItem(data.updateOnePerson);
},
})
}
/> />
</> </>
); );

View File

@ -61,6 +61,7 @@ export const pipelineViewFields: ViewFieldDefinition<ViewFieldMetadata>[] = [
type: 'relation', type: 'relation',
fieldName: 'pointOfContact', fieldName: 'pointOfContact',
relationType: Entity.Person, relationType: Entity.Person,
useEditButton: true,
}, },
isVisible: true, isVisible: true,
} satisfies ViewFieldDefinition<ViewFieldRelationMetadata>, } satisfies ViewFieldDefinition<ViewFieldRelationMetadata>,

View File

@ -64,9 +64,7 @@ export const SEARCH_COMPANY_QUERY = gql`
take: $limit take: $limit
orderBy: $orderBy orderBy: $orderBy
) { ) {
id ...CompanyFieldsFragment
name
domainName
} }
} }
`; `;

View File

@ -1,24 +0,0 @@
import { ReactElement } from 'react';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { BoardCardFieldContext } from '../states/BoardCardFieldContext';
import { BoardCardEditableFieldInternal } from './BoardCardEditableFieldInternal';
type OwnProps = {
editModeContent: ReactElement;
nonEditModeContent: ReactElement;
editModeHorizontalAlign?: 'left' | 'right';
editModeVerticalPosition?: 'over' | 'below';
editHotkeyScope?: HotkeyScope;
};
export function BoardCardEditableField(props: OwnProps) {
return (
<RecoilScope SpecificContext={BoardCardFieldContext}>
<BoardCardEditableFieldInternal {...props} />
</RecoilScope>
);
}

View File

@ -1,39 +0,0 @@
import { useMemo, useState } from 'react';
import { DateInputDisplay } from '@/ui/input/date/components/DateInputDisplay';
import { debounce } from '~/utils/debounce';
import { BoardCardEditableField } from './BoardCardEditableField';
import { BoardCardEditableFieldDateEditMode } from './BoardCardEditableFieldDateEditMode';
type OwnProps = {
value: Date;
onChange: (newValue: Date) => void;
editModeHorizontalAlign?: 'left' | 'right';
};
export function BoardCardEditableFieldDate({
value,
onChange,
editModeHorizontalAlign,
}: OwnProps) {
const [internalValue, setInternalValue] = useState(value);
const debouncedOnChange = useMemo(() => {
return debounce(onChange, 200);
}, [onChange]);
return (
<BoardCardEditableField
editModeHorizontalAlign={editModeHorizontalAlign}
editModeContent={
<BoardCardEditableFieldDateEditMode
value={internalValue}
onChange={(date: Date) => {
setInternalValue(date);
debouncedOnChange(date);
}}
/>
}
nonEditModeContent={<DateInputDisplay value={value} />}
></BoardCardEditableField>
);
}

View File

@ -1,17 +0,0 @@
import { DateInputEdit } from '@/ui/input/date/components/DateInputEdit';
type OwnProps = {
value: Date;
onChange: (newValue: Date) => void;
};
export function BoardCardEditableFieldDateEditMode({
value,
onChange,
}: OwnProps) {
function handleDateChange(newDate: Date) {
onChange(newDate);
}
return <DateInputEdit value={value} onChange={handleDateChange} />;
}

View File

@ -1,32 +0,0 @@
import styled from '@emotion/styled';
export const BoardCardFieldDisplayModeOuterContainer = styled.div`
align-items: center;
display: flex;
height: 100%;
overflow: hidden;
padding-left: ${({ theme }) => theme.spacing(2)};
padding-right: ${({ theme }) => theme.spacing(1)};
width: 100%;
`;
export const BoardCardFieldDisplayModeInnerContainer = styled.div`
align-items: center;
display: flex;
height: 100%;
overflow: hidden;
width: 100%;
`;
export function BoardCardEditableFieldDisplayMode({
children,
}: React.PropsWithChildren<unknown>) {
return (
<BoardCardFieldDisplayModeOuterContainer>
<BoardCardFieldDisplayModeInnerContainer>
{children}
</BoardCardFieldDisplayModeInnerContainer>
</BoardCardFieldDisplayModeOuterContainer>
);
}

View File

@ -1,81 +0,0 @@
import { ReactElement, useRef } from 'react';
import styled from '@emotion/styled';
import { overlayBackground } from '@/ui/theme/constants/effects';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { BoardCardFieldHotkeyScope } from '../types/BoardCardFieldHotkeyScope';
export const BoardCardFieldEditModeContainer = styled.div<
Omit<OwnProps, 'onExit'>
>`
align-items: center;
border: 1px solid ${({ theme }) => theme.border.color.light};
border-radius: ${({ theme }) => theme.border.radius.sm};
display: flex;
left: ${(props) =>
props.editModeHorizontalAlign === 'right' ? 'auto' : '0'};
margin-left: -2px;
min-height: 100%;
min-width: calc(100% + 20px);
position: absolute;
right: ${(props) =>
props.editModeHorizontalAlign === 'right' ? '0' : 'auto'};
top: ${(props) => (props.editModeVerticalPosition === 'over' ? '0' : '100%')};
z-index: 1;
${overlayBackground}
`;
type OwnProps = {
children: ReactElement;
editModeHorizontalAlign?: 'left' | 'right';
editModeVerticalPosition?: 'over' | 'below';
onExit: () => void;
};
export function BoardCardEditableFieldEditMode({
editModeHorizontalAlign,
editModeVerticalPosition,
children,
onExit,
}: OwnProps) {
const wrapperRef = useRef(null);
useListenClickOutside({
refs: [wrapperRef],
callback: () => {
onExit();
},
});
useScopedHotkeys(
'enter',
() => {
onExit();
},
BoardCardFieldHotkeyScope.BoardCardFieldEditMode,
[onExit],
);
useScopedHotkeys(
'esc',
() => {
onExit();
},
BoardCardFieldHotkeyScope.BoardCardFieldEditMode,
[onExit],
);
return (
<BoardCardFieldEditModeContainer
data-testid="editable-cell-edit-mode-container"
ref={wrapperRef}
editModeHorizontalAlign={editModeHorizontalAlign}
editModeVerticalPosition={editModeVerticalPosition}
>
{children}
</BoardCardFieldEditModeContainer>
);
}

View File

@ -1,82 +0,0 @@
import { ReactElement } from 'react';
import styled from '@emotion/styled';
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useBoardCardField } from '../hooks/useBoardCardField';
import { BoardCardFieldHotkeyScope } from '../types/BoardCardFieldHotkeyScope';
import { BoardCardEditableFieldDisplayMode } from './BoardCardEditableFieldDisplayMode';
import { BoardCardEditableFieldEditMode } from './BoardCardEditableFieldEditMode';
export const BoardCardFieldContainer = styled.div`
align-items: center;
box-sizing: border-box;
cursor: pointer;
display: flex;
height: 32px;
position: relative;
user-select: none;
width: 100%;
`;
type OwnProps = {
editModeContent: ReactElement;
nonEditModeContent: ReactElement;
editModeHorizontalAlign?: 'left' | 'right';
editModeVerticalPosition?: 'over' | 'below';
editHotkeyScope?: HotkeyScope;
};
export function BoardCardEditableFieldInternal({
editModeHorizontalAlign = 'left',
editModeVerticalPosition = 'over',
editModeContent,
nonEditModeContent,
editHotkeyScope,
}: OwnProps) {
const { openBoardCardField, isBoardCardFieldInEditMode } =
useBoardCardField();
const { closeBoardCardField } = useBoardCardField();
const {
goBackToPreviousHotkeyScope,
setHotkeyScopeAndMemorizePreviousScope,
} = usePreviousHotkeyScope();
function handleOnClick() {
if (!isBoardCardFieldInEditMode) {
openBoardCardField();
setHotkeyScopeAndMemorizePreviousScope(
editHotkeyScope?.scope ??
BoardCardFieldHotkeyScope.BoardCardFieldEditMode,
editHotkeyScope?.customScopes ?? {},
);
}
}
function handleEditModeExit() {
goBackToPreviousHotkeyScope();
closeBoardCardField();
}
return (
<BoardCardFieldContainer onClick={handleOnClick}>
{isBoardCardFieldInEditMode ? (
<BoardCardEditableFieldEditMode
editModeHorizontalAlign={editModeHorizontalAlign}
editModeVerticalPosition={editModeVerticalPosition}
onExit={handleEditModeExit}
>
{editModeContent}
</BoardCardEditableFieldEditMode>
) : (
<BoardCardEditableFieldDisplayMode>
{nonEditModeContent}
</BoardCardEditableFieldDisplayMode>
)}
</BoardCardFieldContainer>
);
}

View File

@ -1,46 +0,0 @@
import { ChangeEvent, useMemo, useState } from 'react';
import { TextInputDisplay } from '@/ui/input/text/components/TextInputDisplay';
import { StyledInput } from '@/ui/table/editable-cell/type/components/TextCellEdit';
import { debounce } from '~/utils/debounce';
import { BoardCardEditableField } from './BoardCardEditableField';
type OwnProps = {
placeholder?: string;
value: string;
onChange: (newValue: string) => void;
editModeHorizontalAlign?: 'left' | 'right';
};
export function BoardCardEditableFieldText({
value,
placeholder,
onChange,
editModeHorizontalAlign,
}: OwnProps) {
const [internalValue, setInternalValue] = useState(value);
const debouncedOnChange = useMemo(() => {
return debounce(onChange, 200);
}, [onChange]);
return (
<BoardCardEditableField
editModeHorizontalAlign={editModeHorizontalAlign}
editModeContent={
<StyledInput
placeholder={placeholder || ''}
autoFocus
value={internalValue}
autoComplete="off"
onChange={(event: ChangeEvent<HTMLInputElement>) => {
setInternalValue(event.target.value);
debouncedOnChange(event.target.value);
}}
/>
}
nonEditModeContent={<TextInputDisplay>{value}</TextInputDisplay>}
></BoardCardEditableField>
);
}

View File

@ -1,26 +0,0 @@
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { BoardCardFieldContext } from '../states/BoardCardFieldContext';
import { isBoardCardFieldInEditModeScopedState } from '../states/isBoardCardFieldInEditModeScopedState';
export function useBoardCardField() {
const [isBoardCardFieldInEditMode, setIsBoardCardFieldInEditMode] =
useRecoilScopedState(
isBoardCardFieldInEditModeScopedState,
BoardCardFieldContext,
);
function openBoardCardField() {
setIsBoardCardFieldInEditMode(true);
}
function closeBoardCardField() {
setIsBoardCardFieldInEditMode(false);
}
return {
isBoardCardFieldInEditMode,
openBoardCardField,
closeBoardCardField,
};
}

View File

@ -1,3 +0,0 @@
import { createContext } from 'react';
export const BoardCardFieldContext = createContext<string | null>(null);

View File

@ -1,9 +0,0 @@
import { atomFamily } from 'recoil';
export const isBoardCardFieldInEditModeScopedState = atomFamily<
boolean,
string
>({
key: 'isBoardCardFieldInEditModeScopedState',
default: false,
});

View File

@ -1,3 +0,0 @@
export enum BoardCardFieldHotkeyScope {
BoardCardFieldEditMode = 'board-card-field-edit-mode',
}

View File

@ -1,53 +1,40 @@
import { useRecoilCallback } from 'recoil'; import { getOperationName } from '@apollo/client/utilities';
import { useRecoilValue } from 'recoil';
import { GET_PIPELINES } from '@/pipeline/queries';
import { ActionBarEntry } from '@/ui/action-bar/components/ActionBarEntry'; import { ActionBarEntry } from '@/ui/action-bar/components/ActionBarEntry';
import { boardCardIdsByColumnIdFamilyState } from '@/ui/board/states/boardCardIdsByColumnIdFamilyState';
import { boardColumnsState } from '@/ui/board/states/boardColumnsState';
import { selectedBoardCardIdsState } from '@/ui/board/states/selectedBoardCardIdsState';
import { IconTrash } from '@/ui/icon/index'; import { IconTrash } from '@/ui/icon/index';
import { useDeleteManyPipelineProgressMutation } from '~/generated/graphql';
export function BoardActionBarButtonDeleteBoardCard({ import { useRemoveCardIds } from '../hooks/useRemoveCardIds';
onDelete, import { selectedCardIdsSelector } from '../states/selectedCardIdsSelector';
}: {
onDelete: (deletedCardIds: string[]) => void;
}) {
const deleteBoardCardIds = useRecoilCallback(
({ set, snapshot }) =>
() => {
const boardCardIdsToDelete = snapshot
.getLoadable(selectedBoardCardIdsState)
.getValue();
const boardColumns = snapshot.getLoadable(boardColumnsState).getValue(); export function BoardActionBarButtonDeleteBoardCard() {
const selectedCardIds = useRecoilValue(selectedCardIdsSelector);
const removeCardIds = useRemoveCardIds();
for (const boardColumn of boardColumns) { const [deletePipelineProgress] = useDeleteManyPipelineProgressMutation({
const boardColumnCardIds = snapshot refetchQueries: [getOperationName(GET_PIPELINES) ?? ''],
.getLoadable(boardCardIdsByColumnIdFamilyState(boardColumn.id)) });
.getValue();
const newBoardColumnCardIds = boardColumnCardIds.filter( async function handleDelete() {
(cardId) => !boardCardIdsToDelete.includes(cardId), await deletePipelineProgress({
); variables: {
ids: selectedCardIds,
if (newBoardColumnCardIds.length !== boardColumnCardIds.length) {
set(
boardCardIdsByColumnIdFamilyState(boardColumn.id),
newBoardColumnCardIds,
);
}
}
set(selectedBoardCardIdsState, []);
return boardCardIdsToDelete;
}, },
[], optimisticResponse: {
); __typename: 'Mutation',
deleteManyPipelineProgress: {
async function handleDeleteClick() { count: selectedCardIds.length,
const deletedCardIds = deleteBoardCardIds(); },
},
onDelete(deletedCardIds); update: (cache) => {
removeCardIds(selectedCardIds);
selectedCardIds.forEach((id) => {
cache.evict({ id: `PipelineProgress:${id}` });
});
},
});
} }
return ( return (
@ -55,7 +42,7 @@ export function BoardActionBarButtonDeleteBoardCard({
label="Delete" label="Delete"
icon={<IconTrash size={16} />} icon={<IconTrash size={16} />}
type="danger" type="danger"
onClick={handleDeleteClick} onClick={handleDelete}
/> />
); );
} }

View File

@ -4,16 +4,14 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { DragDropContext, OnDragEndResponder } from '@hello-pangea/dnd'; // Atlassian dnd does not support StrictMode from RN 18, so we use a fork @hello-pangea/dnd https://github.com/atlassian/react-beautiful-dnd/issues/2350 import { DragDropContext, OnDragEndResponder } from '@hello-pangea/dnd'; // Atlassian dnd does not support StrictMode from RN 18, so we use a fork @hello-pangea/dnd https://github.com/atlassian/react-beautiful-dnd/issues/2350
import { IconList } from '@tabler/icons-react'; import { IconList } from '@tabler/icons-react';
import { useRecoilState, useSetRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { CompanyBoardContext } from '@/companies/states/CompanyBoardContext'; import { CompanyBoardContext } from '@/companies/states/CompanyBoardContext';
import { GET_PIPELINE_PROGRESS } from '@/pipeline/queries'; import { GET_PIPELINE_PROGRESS } from '@/pipeline/queries';
import { BoardHeader } from '@/ui/board/components/BoardHeader'; import { BoardHeader } from '@/ui/board/components/BoardHeader';
import { StyledBoard } from '@/ui/board/components/StyledBoard'; import { StyledBoard } from '@/ui/board/components/StyledBoard';
import { useUpdateBoardCardIds } from '@/ui/board/hooks/useUpdateBoardCardIds';
import { BoardColumnIdContext } from '@/ui/board/states/BoardColumnIdContext'; import { BoardColumnIdContext } from '@/ui/board/states/BoardColumnIdContext';
import { SelectedSortType } from '@/ui/filter-n-sort/types/interface'; import { SelectedSortType } from '@/ui/filter-n-sort/types/interface';
import { actionBarOpenState } from '@/ui/table/states/ActionBarIsOpenState';
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect'; import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { import {
@ -23,9 +21,10 @@ import {
useUpdateOnePipelineProgressStageMutation, useUpdateOnePipelineProgressStageMutation,
} from '~/generated/graphql'; } from '~/generated/graphql';
import { useSetCardSelected } from '../hooks/useSetCardSelected';
import { useUpdateBoardCardIds } from '../hooks/useUpdateBoardCardIds';
import { BoardColumnContext } from '../states/BoardColumnContext'; import { BoardColumnContext } from '../states/BoardColumnContext';
import { boardColumnsState } from '../states/boardColumnsState'; import { boardColumnsState } from '../states/boardColumnsState';
import { selectedBoardCardIdsState } from '../states/selectedBoardCardIdsState';
import { BoardOptions } from '../types/BoardOptions'; import { BoardOptions } from '../types/BoardOptions';
import { EntityBoardColumn } from './EntityBoardColumn'; import { EntityBoardColumn } from './EntityBoardColumn';
@ -49,6 +48,7 @@ export function EntityBoard({
onEditColumnTitle: (columnId: string, title: string, color: string) => void; onEditColumnTitle: (columnId: string, title: string, color: string) => void;
}) { }) {
const [boardColumns] = useRecoilState(boardColumnsState); const [boardColumns] = useRecoilState(boardColumnsState);
const setCardSelected = useSetCardSelected();
const theme = useTheme(); const theme = useTheme();
const [updatePipelineProgressStage] = const [updatePipelineProgressStage] =
@ -105,21 +105,6 @@ export function EntityBoard({
}); });
const boardRef = useRef(null); const boardRef = useRef(null);
const [selectedBoardCards, setSelectedBoardCards] = useRecoilState(
selectedBoardCardIdsState,
);
const setActionBarOpenState = useSetRecoilState(actionBarOpenState);
function setRowSelectedState(boardCardId: string, selected: boolean) {
if (selected && !selectedBoardCards.includes(boardCardId)) {
setSelectedBoardCards([...selectedBoardCards, boardCardId ?? '']);
setActionBarOpenState(true);
} else if (!selected && selectedBoardCards.includes(boardCardId)) {
setSelectedBoardCards(
selectedBoardCards.filter((id) => id !== boardCardId),
);
}
}
return (boardColumns?.length ?? 0) > 0 ? ( return (boardColumns?.length ?? 0) > 0 ? (
<StyledBoardWithHeader> <StyledBoardWithHeader>
@ -147,7 +132,7 @@ export function EntityBoard({
</StyledBoard> </StyledBoard>
<DragSelect <DragSelect
dragSelectable={boardRef} dragSelectable={boardRef}
onDragSelectionChange={setRowSelectedState} onDragSelectionChange={setCardSelected}
/> />
</StyledBoardWithHeader> </StyledBoardWithHeader>
) : ( ) : (

View File

@ -3,9 +3,9 @@ import { useRecoilValue } from 'recoil';
import { ActionBar } from '@/ui/action-bar/components/ActionBar'; import { ActionBar } from '@/ui/action-bar/components/ActionBar';
import { selectedBoardCardIdsState } from '../states/selectedBoardCardIdsState'; import { selectedCardIdsSelector } from '../states/selectedCardIdsSelector';
export function EntityBoardActionBar() { export function EntityBoardActionBar() {
const selectedBoardCards = useRecoilValue(selectedBoardCardIdsState); const selectedBoardCards = useRecoilValue(selectedCardIdsSelector);
return <ActionBar selectedIds={selectedBoardCards}></ActionBar>; return <ActionBar selectedIds={selectedBoardCards}></ActionBar>;
} }

View File

@ -1,53 +0,0 @@
// TODO: refactor this test with Recoil
describe('getOptimisticlyUpdatedBoard', () => {
it('should return a new board with the updated cell', () => {
// const initialColumn1: string[] = ['item-1', 'item-2', 'item-3'];
// const initialColumn2: string[] = ['item-4', 'item-5'];
// const finalColumn1: string[] = ['item-2', 'item-3'];
// const finalColumn2: string[] = ['item-4', 'item-1', 'item-5'];
// const dropResult = {
// source: {
// droppableId: 'column-1',
// index: 0,
// },
// destination: {
// droppableId: 'column-2',
// index: 1,
// },
// } as DropResult;
// const initialBoard = [
// {
// id: 'column-1',
// title: 'My Column',
// pipelineStageId: 'column-1',
// pipelineProgressIds: initialColumn1,
// },
// {
// id: 'column-2',
// title: 'My Column',
// pipelineStageId: 'column-2',
// pipelineProgressIds: initialColumn2,
// },
// ];
// const updatedBoard = u(
// initialBoard,
// dropResult,
// );
// const finalBoard = [
// {
// id: 'column-1',
// title: 'My Column',
// pipelineStageId: 'column-1',
// pipelineProgressIds: finalColumn1,
// },
// {
// id: 'column-2',
// title: 'My Column',
// pipelineStageId: 'column-2',
// pipelineProgressIds: finalColumn2,
// },
// ];
// expect(updatedBoard).toEqual(finalBoard);
// expect(updatedBoard).not.toBe(initialBoard);
});
});

View File

@ -1,30 +1,13 @@
import { getOperationName } from '@apollo/client/utilities';
import { useSetRecoilState } from 'recoil'; import { useSetRecoilState } from 'recoil';
import { GET_PIPELINES } from '@/pipeline/queries';
import { actionBarEntriesState } from '@/ui/table/states/ActionBarEntriesState'; import { actionBarEntriesState } from '@/ui/table/states/ActionBarEntriesState';
import { useDeleteManyPipelineProgressMutation } from '~/generated/graphql';
import { BoardActionBarButtonDeleteBoardCard } from '../components/BoardActionBarButtonDeleteBoardCard'; import { BoardActionBarButtonDeleteBoardCard } from '../components/BoardActionBarButtonDeleteBoardCard';
export function useOpenActionBar() { export function useOpenActionBar() {
const setActionBarEntries = useSetRecoilState(actionBarEntriesState); const setActionBarEntries = useSetRecoilState(actionBarEntriesState);
const [deletePipelineProgress] = useDeleteManyPipelineProgressMutation({
refetchQueries: [getOperationName(GET_PIPELINES) ?? ''],
});
async function handleDelete(cardIdsToDelete: string[]) {
await deletePipelineProgress({
variables: {
ids: cardIdsToDelete,
},
});
}
return () => { return () => {
setActionBarEntries([ setActionBarEntries([<BoardActionBarButtonDeleteBoardCard />]);
<BoardActionBarButtonDeleteBoardCard onDelete={handleDelete} />,
]);
}; };
} }

View File

@ -0,0 +1,28 @@
import { useContext } from 'react';
import { useRecoilCallback, useRecoilState } from 'recoil';
import { BoardCardIdContext } from '../states/BoardCardIdContext';
import { isCardSelectedFamilyState } from '../states/isCardSelectedFamilyState';
export function useCurrentCardSelected() {
const currentCardId = useContext(BoardCardIdContext);
const [isCardSelected] = useRecoilState(
isCardSelectedFamilyState(currentCardId ?? ''),
);
const setCurrentCardSelected = useRecoilCallback(
({ set }) =>
(selected: boolean) => {
if (!currentCardId) return;
set(isCardSelectedFamilyState(currentCardId), selected);
},
[],
);
return {
currentCardSelected: isCardSelected,
setCurrentCardSelected,
};
}

View File

@ -0,0 +1,27 @@
// Atlassian dnd does not support StrictMode from RN 18, so we use a fork @hello-pangea/dnd https://github.com/atlassian/react-beautiful-dnd/issues/2350
import { useRecoilCallback } from 'recoil';
import { boardCardIdsByColumnIdFamilyState } from '../states/boardCardIdsByColumnIdFamilyState';
import { boardColumnsState } from '../states/boardColumnsState';
export function useRemoveCardIds() {
return useRecoilCallback(
({ snapshot, set }) =>
(cardIdToRemove: string[]) => {
const boardColumns = snapshot
.getLoadable(boardColumnsState)
.valueOrThrow();
boardColumns.forEach((boardColumn) => {
const columnCardIds = snapshot
.getLoadable(boardCardIdsByColumnIdFamilyState(boardColumn.id))
.valueOrThrow();
set(
boardCardIdsByColumnIdFamilyState(boardColumn.id),
columnCardIds.filter((cardId) => !cardIdToRemove.includes(cardId)),
);
});
},
[],
);
}

View File

@ -0,0 +1,27 @@
import { useRecoilCallback } from 'recoil';
import { boardCardIdsByColumnIdFamilyState } from '../states/boardCardIdsByColumnIdFamilyState';
import { boardColumnsState } from '../states/boardColumnsState';
import { isCardSelectedFamilyState } from '../states/isCardSelectedFamilyState';
export function useResetCardSelection() {
return useRecoilCallback(
({ snapshot, set }) =>
() => {
const boardColumns = snapshot
.getLoadable(boardColumnsState)
.valueOrThrow();
const cardIds = boardColumns.flatMap((boardColumn) =>
snapshot
.getLoadable(boardCardIdsByColumnIdFamilyState(boardColumn.id))
.valueOrThrow(),
);
for (const cardId of cardIds) {
set(isCardSelectedFamilyState(cardId), false);
}
},
[],
);
}

View File

@ -0,0 +1,9 @@
import { useRecoilCallback } from 'recoil';
import { isCardSelectedFamilyState } from '../states/isCardSelectedFamilyState';
export function useSetCardSelected() {
return useRecoilCallback(({ set }) => (cardId: string, selected: boolean) => {
set(isCardSelectedFamilyState(cardId), selected);
});
}

View File

@ -1,7 +1,10 @@
import { createContext } from 'react'; import { createContext } from 'react';
import { FieldDefinition } from '@/ui/editable-field/types/FieldDefinition'; import { FieldDefinition } from '@/ui/editable-field/types/FieldDefinition';
import { FieldMetadata } from '@/ui/editable-field/types/FieldMetadata'; import {
FieldMetadata,
FieldType,
} from '@/ui/editable-field/types/FieldMetadata';
export const FieldDefinitionContext = createContext< export const FieldDefinitionContext = createContext<
FieldDefinition<FieldMetadata> FieldDefinition<FieldMetadata>
@ -9,6 +12,6 @@ export const FieldDefinitionContext = createContext<
id: '', id: '',
label: '', label: '',
icon: undefined, icon: undefined,
type: '', type: 'unknown' satisfies FieldType,
metadata: {} as FieldMetadata, metadata: {} as FieldMetadata,
}); });

View File

@ -0,0 +1,6 @@
import { atomFamily } from 'recoil';
export const isCardSelectedFamilyState = atomFamily<boolean, string>({
key: 'isCardSelectedFamilyState',
default: false,
});

View File

@ -1,6 +0,0 @@
import { atom } from 'recoil';
export const selectedBoardCardIdsState = atom<string[]>({
key: 'selectedBoardCardIdsState',
default: [],
});

View File

@ -0,0 +1,22 @@
import { selector } from 'recoil';
import { boardCardIdsByColumnIdFamilyState } from './boardCardIdsByColumnIdFamilyState';
import { boardColumnsState } from './boardColumnsState';
import { isCardSelectedFamilyState } from './isCardSelectedFamilyState';
export const selectedCardIdsSelector = selector<string[]>({
key: 'selectedCardIdsSelector',
get: ({ get }) => {
const boardColumns = get(boardColumnsState);
const cardIds = boardColumns.flatMap((boardColumn) =>
get(boardCardIdsByColumnIdFamilyState(boardColumn.id)),
);
const selectedCardIds = cardIds.filter(
(cardId) => get(isCardSelectedFamilyState(cardId)) === true,
);
return selectedCardIds;
},
});

View File

@ -18,5 +18,5 @@ export const DropdownMenu = styled.div<{
overflow: hidden; overflow: hidden;
width: ${({ width }) => width ?? 160}px; width: ${({ width }) => (width && width > 160 ? width : 160)}px;
`; `;

View File

@ -4,7 +4,6 @@ import { motion } from 'framer-motion';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useBindFieldHotkeyScope } from '../hooks/useBindFieldHotkeyScope';
import { useEditableField } from '../hooks/useEditableField'; import { useEditableField } from '../hooks/useEditableField';
import { EditableFieldDisplayMode } from './EditableFieldDisplayMode'; import { EditableFieldDisplayMode } from './EditableFieldDisplayMode';
@ -74,7 +73,6 @@ type OwnProps = {
displayModeContentOnly?: boolean; displayModeContentOnly?: boolean;
disableHoverEffect?: boolean; disableHoverEffect?: boolean;
displayModeContent: React.ReactNode; displayModeContent: React.ReactNode;
parentHotkeyScope?: HotkeyScope;
customEditHotkeyScope?: HotkeyScope; customEditHotkeyScope?: HotkeyScope;
isDisplayModeContentEmpty?: boolean; isDisplayModeContentEmpty?: boolean;
isDisplayModeFixHeight?: boolean; isDisplayModeFixHeight?: boolean;
@ -89,7 +87,6 @@ export function EditableField({
useEditButton, useEditButton,
editModeContent, editModeContent,
displayModeContent, displayModeContent,
parentHotkeyScope,
customEditHotkeyScope, customEditHotkeyScope,
disableHoverEffect, disableHoverEffect,
isDisplayModeContentEmpty, isDisplayModeContentEmpty,
@ -100,11 +97,6 @@ export function EditableField({
}: OwnProps) { }: OwnProps) {
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState(false);
useBindFieldHotkeyScope({
customEditHotkeyScope,
parentHotkeyScope,
});
function handleContainerMouseEnter() { function handleContainerMouseEnter() {
setIsHovered(true); setIsHovered(true);
} }
@ -116,7 +108,7 @@ export function EditableField({
const { isFieldInEditMode, openEditableField } = useEditableField(); const { isFieldInEditMode, openEditableField } = useEditableField();
function handleDisplayModeClick() { function handleDisplayModeClick() {
openEditableField(); openEditableField(customEditHotkeyScope);
} }
const showEditButton = !isFieldInEditMode && isHovered && useEditButton; const showEditButton = !isFieldInEditMode && isHovered && useEditButton;

View File

@ -1,64 +0,0 @@
import { useEffect, useState } from 'react';
import { EditableField } from '@/ui/editable-field/components/EditableField';
import { FieldContext } from '@/ui/editable-field/states/FieldContext';
import { IconMap } from '@/ui/icon';
import { TextInputEdit } from '@/ui/input/text/components/TextInputEdit';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { Company, useUpdateOneCompanyMutation } from '~/generated/graphql';
type OwnProps = {
company: Pick<Company, 'id' | 'address'>;
};
export function CompanyEditableFieldAddress({ company }: OwnProps) {
const [internalValue, setInternalValue] = useState(company.address);
const [updateCompany] = useUpdateOneCompanyMutation();
useEffect(() => {
setInternalValue(company.address);
}, [company.address]);
async function handleChange(newValue: string) {
setInternalValue(newValue);
}
async function handleSubmit() {
await updateCompany({
variables: {
where: {
id: company.id,
},
data: {
address: internalValue ?? '',
},
},
});
}
async function handleCancel() {
setInternalValue(company.address);
}
return (
<RecoilScope SpecificContext={FieldContext}>
<EditableField
onSubmit={handleSubmit}
onCancel={handleCancel}
iconLabel={<IconMap />}
editModeContent={
<TextInputEdit
placeholder={'Address'}
autoFocus
value={internalValue}
onChange={(newValue: string) => {
handleChange(newValue);
}}
/>
}
displayModeContent={internalValue !== '' ? internalValue : 'No address'}
/>
</RecoilScope>
);
}

View File

@ -3,12 +3,18 @@ import { useContext } from 'react';
import { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext'; import { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext';
import { isFieldDate } from '../types/guards/isFieldDate'; import { isFieldDate } from '../types/guards/isFieldDate';
import { isFieldNumber } from '../types/guards/isFieldNumber'; import { isFieldNumber } from '../types/guards/isFieldNumber';
import { isFieldPhone } from '../types/guards/isFieldPhone';
import { isFieldProbability } from '../types/guards/isFieldProbability'; import { isFieldProbability } from '../types/guards/isFieldProbability';
import { isFieldRelation } from '../types/guards/isFieldRelation'; import { isFieldRelation } from '../types/guards/isFieldRelation';
import { isFieldText } from '../types/guards/isFieldText';
import { isFieldURL } from '../types/guards/isFieldURL';
import { GenericEditableDateField } from './GenericEditableDateField'; import { GenericEditableDateField } from './GenericEditableDateField';
import { GenericEditableNumberField } from './GenericEditableNumberField'; import { GenericEditableNumberField } from './GenericEditableNumberField';
import { GenericEditablePhoneField } from './GenericEditablePhoneField';
import { GenericEditableRelationField } from './GenericEditableRelationField'; import { GenericEditableRelationField } from './GenericEditableRelationField';
import { GenericEditableTextField } from './GenericEditableTextField';
import { GenericEditableURLField } from './GenericEditableURLField';
import { ProbabilityEditableField } from './ProbabilityEditableField'; import { ProbabilityEditableField } from './ProbabilityEditableField';
export function GenericEditableField() { export function GenericEditableField() {
@ -22,9 +28,15 @@ export function GenericEditableField() {
return <GenericEditableNumberField />; return <GenericEditableNumberField />;
} else if (isFieldProbability(fieldDefinition)) { } else if (isFieldProbability(fieldDefinition)) {
return <ProbabilityEditableField />; return <ProbabilityEditableField />;
} else if (isFieldURL(fieldDefinition)) {
return <GenericEditableURLField />;
} else if (isFieldText(fieldDefinition)) {
return <GenericEditableTextField />;
} else if (isFieldPhone(fieldDefinition)) {
return <GenericEditablePhoneField />;
} else { } else {
console.warn( console.warn(
`Unknown field metadata type: ${fieldDefinition.metadata.type} in GenericEditableCell`, `Unknown field metadata type: ${fieldDefinition.type} in GenericEditableField`,
); );
return <></>; return <></>;
} }

View File

@ -69,6 +69,7 @@ export function GenericEditableNumberFieldEditMode() {
<div ref={wrapperRef}> <div ref={wrapperRef}>
<TextInputEdit <TextInputEdit
autoFocus autoFocus
placeholder={currentEditableFieldDefinition.metadata.placeHolder}
value={internalValue ? internalValue.toString() : ''} value={internalValue ? internalValue.toString() : ''}
onChange={(newValue: string) => { onChange={(newValue: string) => {
handleChange(newValue); handleChange(newValue);

View File

@ -0,0 +1,43 @@
import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
import { PhoneInputDisplay } from '@/ui/input/phone/components/PhoneInputDisplay';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext';
import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext';
import { FieldContext } from '../states/FieldContext';
import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
import { FieldDefinition } from '../types/FieldDefinition';
import { FieldPhoneMetadata } from '../types/FieldMetadata';
import { EditableField } from './EditableField';
import { GenericEditablePhoneFieldEditMode } from './GenericEditablePhoneFieldEditMode';
export function GenericEditablePhoneField() {
const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext);
const currentEditableFieldDefinition = useContext(
EditableFieldDefinitionContext,
) as FieldDefinition<FieldPhoneMetadata>;
const fieldValue = useRecoilValue<string>(
genericEntityFieldFamilySelector({
entityId: currentEditableFieldEntityId ?? '',
fieldName: currentEditableFieldDefinition
? currentEditableFieldDefinition.metadata.fieldName
: '',
}),
);
return (
<RecoilScope SpecificContext={FieldContext}>
<EditableField
useEditButton
iconLabel={currentEditableFieldDefinition.icon}
editModeContent={<GenericEditablePhoneFieldEditMode />}
displayModeContent={<PhoneInputDisplay value={fieldValue} />}
isDisplayModeContentEmpty={!fieldValue}
/>
</RecoilScope>
);
}

View File

@ -0,0 +1,72 @@
import { useContext, useRef, useState } from 'react';
import { useRecoilState } from 'recoil';
import { TextInputEdit } from '@/ui/input/text/components/TextInputEdit';
import { useRegisterCloseFieldHandlers } from '../hooks/useRegisterCloseFieldHandlers';
import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField';
import { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext';
import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext';
import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
import { FieldDefinition } from '../types/FieldDefinition';
import { FieldPhoneMetadata } from '../types/FieldMetadata';
export function GenericEditablePhoneFieldEditMode() {
const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext);
const currentEditableFieldDefinition = useContext(
EditableFieldDefinitionContext,
) as FieldDefinition<FieldPhoneMetadata>;
// TODO: we could use a hook that would return the field value with the right type
const [fieldValue, setFieldValue] = useRecoilState<string>(
genericEntityFieldFamilySelector({
entityId: currentEditableFieldEntityId ?? '',
fieldName: currentEditableFieldDefinition
? currentEditableFieldDefinition.metadata.fieldName
: '',
}),
);
const [internalValue, setInternalValue] = useState(fieldValue);
const updateField = useUpdateGenericEntityField();
const wrapperRef = useRef(null);
useRegisterCloseFieldHandlers(wrapperRef, handleSubmit, onCancel);
function handleSubmit() {
if (internalValue === fieldValue) return;
setFieldValue(internalValue);
if (currentEditableFieldEntityId && updateField) {
updateField(
currentEditableFieldEntityId,
currentEditableFieldDefinition,
internalValue,
);
}
}
function onCancel() {
setFieldValue(fieldValue);
}
function handleChange(newValue: string) {
setInternalValue(newValue);
}
return (
<div ref={wrapperRef}>
<TextInputEdit
autoFocus
placeholder={currentEditableFieldDefinition.metadata.placeHolder}
value={internalValue}
onChange={(newValue: string) => {
handleChange(newValue);
}}
/>
</div>
);
}

View File

@ -34,7 +34,7 @@ export function GenericEditableRelationField() {
<RecoilScope SpecificContext={FieldContext}> <RecoilScope SpecificContext={FieldContext}>
<RecoilScope> <RecoilScope>
<EditableField <EditableField
useEditButton useEditButton={currentEditableFieldDefinition.metadata.useEditButton}
customEditHotkeyScope={{ customEditHotkeyScope={{
scope: RelationPickerHotkeyScope.RelationPicker, scope: RelationPickerHotkeyScope.RelationPicker,
}} }}

View File

@ -1,8 +1,11 @@
import { useContext } from 'react'; import { useContext } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { CompanyChip } from '@/companies/components/CompanyChip';
import { PersonChip } from '@/people/components/PersonChip'; import { PersonChip } from '@/people/components/PersonChip';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { UserChip } from '@/users/components/UserChip';
import { getLogoUrlFromDomainName } from '~/utils';
import { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext'; import { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext';
import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext'; import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext';
@ -35,6 +38,28 @@ export function GenericEditableRelationFieldDisplayMode() {
/> />
); );
} }
case Entity.User: {
return (
<UserChip
id={fieldValue?.id ?? ''}
name={fieldValue?.displayName ?? ''}
pictureUrl={fieldValue?.avatarUrl ?? ''}
/>
);
}
case Entity.Company: {
return (
<CompanyChip
id={fieldValue?.id ?? ''}
name={fieldValue?.name ?? ''}
pictureUrl={
fieldValue?.domainName
? getLogoUrlFromDomainName(fieldValue.domainName)
: ''
}
/>
);
}
default: default:
console.warn( console.warn(
`Unknown relation type: "${currentEditableFieldDefinition.metadata.relationType}" `Unknown relation type: "${currentEditableFieldDefinition.metadata.relationType}"

View File

@ -2,10 +2,11 @@ import { useContext } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { CompanyPicker } from '@/companies/components/CompanyPicker';
import { PeoplePicker } from '@/people/components/PeoplePicker'; import { PeoplePicker } from '@/people/components/PeoplePicker';
import { ViewFieldRelationValue } from '@/ui/editable-field/types/ViewField';
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect'; import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect'; import { Entity } from '@/ui/input/relation-picker/types/EntityTypeForSelect';
import { UserPicker } from '@/users/components/UserPicker';
import { useEditableField } from '../hooks/useEditableField'; import { useEditableField } from '../hooks/useEditableField';
import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField'; import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField';
@ -13,7 +14,10 @@ import { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitio
import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext'; import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext';
import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector'; import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
import { FieldDefinition } from '../types/FieldDefinition'; import { FieldDefinition } from '../types/FieldDefinition';
import { FieldRelationMetadata } from '../types/FieldMetadata'; import {
FieldRelationMetadata,
FieldRelationValue,
} from '../types/FieldMetadata';
const RelationPickerContainer = styled.div` const RelationPickerContainer = styled.div`
left: 0px; left: 0px;
@ -28,7 +32,7 @@ function RelationPicker({
handleCancel, handleCancel,
}: { }: {
fieldDefinition: FieldDefinition<FieldRelationMetadata>; fieldDefinition: FieldDefinition<FieldRelationMetadata>;
fieldValue: ViewFieldRelationValue; fieldValue: FieldRelationValue;
handleEntitySubmit: (newRelationId: EntityForSelect | null) => void; handleEntitySubmit: (newRelationId: EntityForSelect | null) => void;
handleCancel: () => void; handleCancel: () => void;
}) { }) {
@ -36,7 +40,25 @@ function RelationPicker({
case Entity.Person: { case Entity.Person: {
return ( return (
<PeoplePicker <PeoplePicker
personId={fieldValue?.id ?? null} personId={fieldValue ? fieldValue.id : ''}
onSubmit={handleEntitySubmit}
onCancel={handleCancel}
/>
);
}
case Entity.User: {
return (
<UserPicker
userId={fieldValue ? fieldValue.id : ''}
onSubmit={handleEntitySubmit}
onCancel={handleCancel}
/>
);
}
case Entity.Company: {
return (
<CompanyPicker
companyId={fieldValue ? fieldValue.id : ''}
onSubmit={handleEntitySubmit} onSubmit={handleEntitySubmit}
onCancel={handleCancel} onCancel={handleCancel}
/> />
@ -46,7 +68,7 @@ function RelationPicker({
console.warn( console.warn(
`Unknown relation type: "${fieldDefinition.metadata.relationType}" in GenericEditableRelationField`, `Unknown relation type: "${fieldDefinition.metadata.relationType}" in GenericEditableRelationField`,
); );
return <> </>; return <></>;
} }
} }

View File

@ -0,0 +1,41 @@
import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext';
import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext';
import { FieldContext } from '../states/FieldContext';
import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
import { FieldDefinition } from '../types/FieldDefinition';
import { FieldNumberMetadata } from '../types/FieldMetadata';
import { EditableField } from './EditableField';
import { GenericEditableTextFieldEditMode } from './GenericEditableTextFieldEditMode';
export function GenericEditableTextField() {
const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext);
const currentEditableFieldDefinition = useContext(
EditableFieldDefinitionContext,
) as FieldDefinition<FieldNumberMetadata>;
const fieldValue = useRecoilValue<string>(
genericEntityFieldFamilySelector({
entityId: currentEditableFieldEntityId ?? '',
fieldName: currentEditableFieldDefinition
? currentEditableFieldDefinition.metadata.fieldName
: '',
}),
);
return (
<RecoilScope SpecificContext={FieldContext}>
<EditableField
iconLabel={currentEditableFieldDefinition.icon}
editModeContent={<GenericEditableTextFieldEditMode />}
displayModeContent={fieldValue}
isDisplayModeContentEmpty={!fieldValue}
/>
</RecoilScope>
);
}

View File

@ -0,0 +1,72 @@
import { useContext, useRef, useState } from 'react';
import { useRecoilState } from 'recoil';
import { TextInputEdit } from '@/ui/input/text/components/TextInputEdit';
import { useRegisterCloseFieldHandlers } from '../hooks/useRegisterCloseFieldHandlers';
import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField';
import { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext';
import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext';
import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
import { FieldDefinition } from '../types/FieldDefinition';
import { FieldTextMetadata } from '../types/FieldMetadata';
export function GenericEditableTextFieldEditMode() {
const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext);
const currentEditableFieldDefinition = useContext(
EditableFieldDefinitionContext,
) as FieldDefinition<FieldTextMetadata>;
// TODO: we could use a hook that would return the field value with the right type
const [fieldValue, setFieldValue] = useRecoilState<string>(
genericEntityFieldFamilySelector({
entityId: currentEditableFieldEntityId ?? '',
fieldName: currentEditableFieldDefinition
? currentEditableFieldDefinition.metadata.fieldName
: '',
}),
);
const [internalValue, setInternalValue] = useState(fieldValue);
const updateField = useUpdateGenericEntityField();
const wrapperRef = useRef(null);
useRegisterCloseFieldHandlers(wrapperRef, handleSubmit, onCancel);
function handleSubmit() {
if (internalValue === fieldValue) return;
setFieldValue(internalValue);
if (currentEditableFieldEntityId && updateField) {
updateField(
currentEditableFieldEntityId,
currentEditableFieldDefinition,
internalValue,
);
}
}
function onCancel() {
setFieldValue(fieldValue);
}
function handleChange(newValue: string) {
setInternalValue(newValue);
}
return (
<div ref={wrapperRef}>
<TextInputEdit
autoFocus
placeholder={currentEditableFieldDefinition.metadata.placeHolder}
value={internalValue}
onChange={(newValue: string) => {
handleChange(newValue);
}}
/>
</div>
);
}

View File

@ -0,0 +1,43 @@
import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext';
import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext';
import { FieldContext } from '../states/FieldContext';
import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
import { FieldDefinition } from '../types/FieldDefinition';
import { FieldNumberMetadata } from '../types/FieldMetadata';
import { EditableField } from './EditableField';
import { FieldDisplayURL } from './FieldDisplayURL';
import { GenericEditableURLFieldEditMode } from './GenericEditableURLFieldEditMode';
export function GenericEditableURLField() {
const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext);
const currentEditableFieldDefinition = useContext(
EditableFieldDefinitionContext,
) as FieldDefinition<FieldNumberMetadata>;
const fieldValue = useRecoilValue<string>(
genericEntityFieldFamilySelector({
entityId: currentEditableFieldEntityId ?? '',
fieldName: currentEditableFieldDefinition
? currentEditableFieldDefinition.metadata.fieldName
: '',
}),
);
return (
<RecoilScope SpecificContext={FieldContext}>
<EditableField
useEditButton
iconLabel={currentEditableFieldDefinition.icon}
editModeContent={<GenericEditableURLFieldEditMode />}
displayModeContent={<FieldDisplayURL URL={fieldValue} />}
isDisplayModeContentEmpty={!fieldValue}
/>
</RecoilScope>
);
}

View File

@ -0,0 +1,74 @@
import { useContext, useRef, useState } from 'react';
import { useRecoilState } from 'recoil';
import { TextInputEdit } from '@/ui/input/text/components/TextInputEdit';
import { useRegisterCloseFieldHandlers } from '../hooks/useRegisterCloseFieldHandlers';
import { useUpdateGenericEntityField } from '../hooks/useUpdateGenericEntityField';
import { EditableFieldDefinitionContext } from '../states/EditableFieldDefinitionContext';
import { EditableFieldEntityIdContext } from '../states/EditableFieldEntityIdContext';
import { genericEntityFieldFamilySelector } from '../states/genericEntityFieldFamilySelector';
import { FieldDefinition } from '../types/FieldDefinition';
import { FieldURLMetadata } from '../types/FieldMetadata';
// This one is very similar to GenericEditableTextFieldEditMode
// We could probably merge them since FieldURLMetadata is basically a FieldTextMetadata
export function GenericEditableURLFieldEditMode() {
const currentEditableFieldEntityId = useContext(EditableFieldEntityIdContext);
const currentEditableFieldDefinition = useContext(
EditableFieldDefinitionContext,
) as FieldDefinition<FieldURLMetadata>;
// TODO: we could use a hook that would return the field value with the right type
const [fieldValue, setFieldValue] = useRecoilState<string>(
genericEntityFieldFamilySelector({
entityId: currentEditableFieldEntityId ?? '',
fieldName: currentEditableFieldDefinition
? currentEditableFieldDefinition.metadata.fieldName
: '',
}),
);
const [internalValue, setInternalValue] = useState(fieldValue);
const updateField = useUpdateGenericEntityField();
const wrapperRef = useRef(null);
useRegisterCloseFieldHandlers(wrapperRef, handleSubmit, onCancel);
function handleSubmit() {
if (internalValue === fieldValue) return;
setFieldValue(internalValue);
if (currentEditableFieldEntityId && updateField) {
updateField(
currentEditableFieldEntityId,
currentEditableFieldDefinition,
internalValue,
);
}
}
function onCancel() {
setFieldValue(fieldValue);
}
function handleChange(newValue: string) {
setInternalValue(newValue);
}
return (
<div ref={wrapperRef}>
<TextInputEdit
autoFocus
placeholder={currentEditableFieldDefinition.metadata.placeHolder}
value={internalValue}
onChange={(newValue: string) => {
handleChange(newValue);
}}
/>
</div>
);
}

View File

@ -1,75 +0,0 @@
import { useEffect } from 'react';
import { useRecoilCallback } from 'recoil';
import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { isSameHotkeyScope } from '@/ui/utilities/hotkey/utils/isSameHotkeyScope';
import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { getSnapshotScopedState } from '@/ui/utilities/recoil-scope/utils/getSnapshotScopedState';
import { getSnapshotState } from '@/ui/utilities/recoil-scope/utils/getSnapshotState';
import { customEditHotkeyScopeForFieldScopedState } from '../states/customEditHotkeyScopeForFieldScopedState';
import { FieldContext } from '../states/FieldContext';
import { parentHotkeyScopeForFieldScopedState } from '../states/parentHotkeyScopeForFieldScopedState';
export function useBindFieldHotkeyScope({
customEditHotkeyScope,
parentHotkeyScope,
}: {
customEditHotkeyScope?: HotkeyScope;
parentHotkeyScope?: HotkeyScope;
}) {
const [customEditHotkeyScopeForField, setCustomEditHotkeyScopeForField] =
useRecoilScopedState(
customEditHotkeyScopeForFieldScopedState,
FieldContext,
);
const fieldContextScopeId = useContextScopeId(FieldContext);
useEffect(() => {
if (
customEditHotkeyScope &&
!isSameHotkeyScope(customEditHotkeyScope, customEditHotkeyScopeForField)
) {
setCustomEditHotkeyScopeForField(customEditHotkeyScope);
}
}, [
customEditHotkeyScope,
customEditHotkeyScopeForField,
setCustomEditHotkeyScopeForField,
]);
const setParentHotkeyScopeForField = useRecoilCallback(
({ snapshot, set }) =>
(parentHotkeyScopeToSet: HotkeyScope | null | undefined) => {
const currentHotkeyScope = getSnapshotState({
snapshot,
state: currentHotkeyScopeState,
});
const parentHotkeyScopeForField = getSnapshotScopedState({
snapshot,
state: parentHotkeyScopeForFieldScopedState,
contextScopeId: fieldContextScopeId,
});
if (!parentHotkeyScopeToSet) {
set(
parentHotkeyScopeForFieldScopedState(fieldContextScopeId),
currentHotkeyScope,
);
} else if (
!isSameHotkeyScope(parentHotkeyScopeToSet, parentHotkeyScopeForField)
) {
setParentHotkeyScopeForField(parentHotkeyScopeToSet);
}
},
[fieldContextScopeId],
);
useEffect(() => {
setParentHotkeyScopeForField(parentHotkeyScope);
}, [parentHotkeyScope, setParentHotkeyScopeForField]);
}

View File

@ -1,10 +1,9 @@
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState'; import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { customEditHotkeyScopeForFieldScopedState } from '../states/customEditHotkeyScopeForFieldScopedState';
import { FieldContext } from '../states/FieldContext'; import { FieldContext } from '../states/FieldContext';
import { isFieldInEditModeScopedState } from '../states/isFieldInEditModeScopedState'; import { isFieldInEditModeScopedState } from '../states/isFieldInEditModeScopedState';
import { parentHotkeyScopeForFieldScopedState } from '../states/parentHotkeyScopeForFieldScopedState';
import { EditableFieldHotkeyScope } from '../types/EditableFieldHotkeyScope'; import { EditableFieldHotkeyScope } from '../types/EditableFieldHotkeyScope';
export function useEditableField() { export function useEditableField() {
@ -13,39 +12,29 @@ export function useEditableField() {
FieldContext, FieldContext,
); );
const [customEditHotkeyScopeForField] = useRecoilScopedState( const {
customEditHotkeyScopeForFieldScopedState, setHotkeyScopeAndMemorizePreviousScope,
FieldContext, goBackToPreviousHotkeyScope,
); } = usePreviousHotkeyScope();
const [parentHotkeyScopeForField] = useRecoilScopedState(
parentHotkeyScopeForFieldScopedState,
FieldContext,
);
const setHotkeyScope = useSetHotkeyScope();
function closeEditableField() { function closeEditableField() {
setIsFieldInEditMode(false); setIsFieldInEditMode(false);
if (parentHotkeyScopeForField) { goBackToPreviousHotkeyScope();
setHotkeyScope(
parentHotkeyScopeForField.scope,
parentHotkeyScopeForField.customScopes,
);
}
} }
function openEditableField() { function openEditableField(customEditHotkeyScopeForField?: HotkeyScope) {
setIsFieldInEditMode(true); setIsFieldInEditMode(true);
if (customEditHotkeyScopeForField) { if (customEditHotkeyScopeForField) {
setHotkeyScope( setHotkeyScopeAndMemorizePreviousScope(
customEditHotkeyScopeForField.scope, customEditHotkeyScopeForField.scope,
customEditHotkeyScopeForField.customScopes, customEditHotkeyScopeForField.customScopes,
); );
} else { } else {
setHotkeyScope(EditableFieldHotkeyScope.EditableField); setHotkeyScopeAndMemorizePreviousScope(
EditableFieldHotkeyScope.EditableField,
);
} }
} }

View File

@ -1,7 +1,5 @@
import { useContext } from 'react'; import { useContext } from 'react';
import { isFieldChip } from '@/ui/editable-field/types/guards/isFieldChip';
import { EditableFieldMutationContext } from '../states/EditableFieldMutationContext'; import { EditableFieldMutationContext } from '../states/EditableFieldMutationContext';
import { FieldDefinition } from '../types/FieldDefinition'; import { FieldDefinition } from '../types/FieldDefinition';
import { import {
@ -27,6 +25,7 @@ import {
FieldURLMetadata, FieldURLMetadata,
FieldURLValue, FieldURLValue,
} from '../types/FieldMetadata'; } from '../types/FieldMetadata';
import { isFieldChip } from '../types/guards/isFieldChip';
import { isFieldChipValue } from '../types/guards/isFieldChipValue'; import { isFieldChipValue } from '../types/guards/isFieldChipValue';
import { isFieldDate } from '../types/guards/isFieldDate'; import { isFieldDate } from '../types/guards/isFieldDate';
import { isFieldDateValue } from '../types/guards/isFieldDateValue'; import { isFieldDateValue } from '../types/guards/isFieldDateValue';
@ -53,31 +52,30 @@ export function useUpdateGenericEntityField() {
const [updateEntity] = useUpdateEntityMutation(); const [updateEntity] = useUpdateEntityMutation();
return function updateEntityField< return function updateEntityField<
MetadataType extends FieldMetadata, ValueType extends FieldMetadata extends FieldDoubleTextMetadata
ValueType extends MetadataType extends FieldDoubleTextMetadata
? FieldDoubleTextValue ? FieldDoubleTextValue
: MetadataType extends FieldTextMetadata : FieldMetadata extends FieldTextMetadata
? FieldTextValue ? FieldTextValue
: MetadataType extends FieldPhoneMetadata : FieldMetadata extends FieldPhoneMetadata
? FieldPhoneValue ? FieldPhoneValue
: MetadataType extends FieldURLMetadata : FieldMetadata extends FieldURLMetadata
? FieldURLValue ? FieldURLValue
: MetadataType extends FieldNumberMetadata : FieldMetadata extends FieldNumberMetadata
? FieldNumberValue ? FieldNumberValue
: MetadataType extends FieldDateMetadata : FieldMetadata extends FieldDateMetadata
? FieldDateValue ? FieldDateValue
: MetadataType extends FieldChipMetadata : FieldMetadata extends FieldChipMetadata
? FieldChipValue ? FieldChipValue
: MetadataType extends FieldDoubleTextChipMetadata : FieldMetadata extends FieldDoubleTextChipMetadata
? FieldDoubleTextChipValue ? FieldDoubleTextChipValue
: MetadataType extends FieldRelationMetadata : FieldMetadata extends FieldRelationMetadata
? FieldRelationValue ? FieldRelationValue
: MetadataType extends FieldProbabilityMetadata : FieldMetadata extends FieldProbabilityMetadata
? FieldProbabilityValue ? FieldProbabilityValue
: unknown, : unknown,
>( >(
currentEntityId: string, currentEntityId: string,
field: FieldDefinition<MetadataType>, field: FieldDefinition<FieldMetadata>,
newFieldValue: ValueType, newFieldValue: ValueType,
) { ) {
const newFieldValueUnknown = newFieldValue as unknown; const newFieldValueUnknown = newFieldValue as unknown;

Some files were not shown because too many files have changed in this diff Show More