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
@@ -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.
+2 -1
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>
+2 -2
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).
+1
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',
{ {
+90 -241
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) {
@@ -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;
@@ -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>
);
}
@@ -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>
); );
} }
@@ -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)}
@@ -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 = {};
@@ -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>
@@ -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],
); );
@@ -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)
: []; : [];
@@ -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);
}, },
}); });
@@ -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);
}; };
} }
@@ -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);
}; };
} }
@@ -45,8 +45,6 @@ export const CREATE_ACTIVITY_WITH_COMMENT = gql`
createdAt createdAt
updatedAt updatedAt
activityId activityId
commentableType
commentableId
companyId companyId
personId personId
} }
+64 -111
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
}
} }
} }
`; `;
@@ -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
} }
@@ -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,
}} }}
/> />
))} ))}
@@ -0,0 +1,10 @@
import { atom } from 'recoil';
import { ActivityTargetableEntity } from '../types/ActivityTargetableEntity';
export const activityTargetableEntityArrayState = atom<
ActivityTargetableEntity[]
>({
key: 'activities/targetable-entity-array',
default: [],
});
@@ -1,8 +0,0 @@
import { atom } from 'recoil';
import { CommentableEntity } from '../types/CommentableEntity';
export const commentableEntityArrayState = atom<CommentableEntity[]>({
key: 'activities/commentable-entity-array',
default: [],
});
@@ -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({
@@ -0,0 +1,9 @@
export enum ActivityTargetableEntityType {
Person = 'Person',
Company = 'Company',
}
export type ActivityTargetableEntity = {
id: string;
type: ActivityTargetableEntityType;
};
@@ -0,0 +1,7 @@
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { ActivityTargetableEntityType } from './ActivityTargetableEntity';
export type ActivityTargetableEntityForSelect = EntityForSelect & {
entityType: ActivityTargetableEntityType;
};
@@ -1,6 +0,0 @@
import { CommentableType } from '~/generated/graphql';
export type CommentableEntity = {
id: string;
type: CommentableType;
};
@@ -1,6 +0,0 @@
import { EntityForSelect } from '@/ui/input/relation-picker/types/EntityForSelect';
import { CommentableType } from '~/generated/graphql';
export type CommentableEntityForSelect = EntityForSelect & {
entityType: CommentableType;
};
@@ -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}
@@ -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>;
@@ -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>
@@ -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],
}}
/>
);
}
@@ -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);
}); });
@@ -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>
);
}
@@ -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>
);
}
@@ -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>
);
}
@@ -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>
);
}
@@ -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>
);
}
@@ -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>
);
}
@@ -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);
@@ -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);
@@ -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,
}); });
+11 -1
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);
},
});
} }
+20 -25
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
} }
} }
`; `;
@@ -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);
},
})
}
/> />
</> </>
); );
@@ -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 <></>;
} }
@@ -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()]}
/> />
</> </>
); );
@@ -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,
}}
/>
);
}
@@ -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>
);
}
@@ -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>
),
};
@@ -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>
);
}
@@ -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>
);
}
@@ -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);
@@ -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);
@@ -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);
+4 -4
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,
}); });
+11 -1
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);
},
});
} }
@@ -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);
},
})
}
/> />
</> </>
); );
@@ -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>,
+1 -3
View File
@@ -64,9 +64,7 @@ export const SEARCH_COMPANY_QUERY = gql`
take: $limit take: $limit
orderBy: $orderBy orderBy: $orderBy
) { ) {
id ...CompanyFieldsFragment
name
domainName
} }
} }
`; `;
@@ -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>
);
}
@@ -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>
);
}
@@ -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} />;
}
@@ -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>
);
}
@@ -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>
);
}
@@ -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>
);
}
@@ -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>
);
}
@@ -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,
};
}
@@ -1,3 +0,0 @@
import { createContext } from 'react';
export const BoardCardFieldContext = createContext<string | null>(null);
@@ -1,9 +0,0 @@
import { atomFamily } from 'recoil';
export const isBoardCardFieldInEditModeScopedState = atomFamily<
boolean,
string
>({
key: 'isBoardCardFieldInEditModeScopedState',
default: false,
});
@@ -1,3 +0,0 @@
export enum BoardCardFieldHotkeyScope {
BoardCardFieldEditMode = 'board-card-field-edit-mode',
}
@@ -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}
/> />
); );
} }
@@ -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>
) : ( ) : (
@@ -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>;
} }
@@ -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);
});
});
@@ -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} />,
]);
}; };
} }
@@ -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,
};
}
@@ -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)),
);
});
},
[],
);
}
@@ -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);
}
},
[],
);
}
@@ -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);
});
}
@@ -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,
}); });
@@ -0,0 +1,6 @@
import { atomFamily } from 'recoil';
export const isCardSelectedFamilyState = atomFamily<boolean, string>({
key: 'isCardSelectedFamilyState',
default: false,
});
@@ -1,6 +0,0 @@
import { atom } from 'recoil';
export const selectedBoardCardIdsState = atom<string[]>({
key: 'selectedBoardCardIdsState',
default: [],
});
@@ -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;
},
});
@@ -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;
`; `;
@@ -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;
@@ -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>
);
}
@@ -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 <></>;
} }
@@ -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);
@@ -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>
);
}
@@ -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>
);
}
@@ -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,
}} }}
@@ -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}"
@@ -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 <></>;
} }
} }
@@ -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>
);
}
@@ -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>
);
}
@@ -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>
);
}
@@ -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>
);
}
@@ -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]);
}
@@ -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,
);
} }
} }
@@ -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