diff --git a/.vscode/settings.json b/.vscode/settings.json index 9696c8c807..2311f19166 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -31,6 +31,7 @@ "!javascript", "!json", "!typescript", + "!typescriptreact", "md", "mdx" ], diff --git a/front/craco.config.js b/front/craco.config.js index 5df42ad774..9f9efefe43 100644 --- a/front/craco.config.js +++ b/front/craco.config.js @@ -1,12 +1,24 @@ const path = require("path"); module.exports = { + devServer: { + client: { + overlay: { + runtimeErrors: (error) => { + if (error.message === "ResizeObserver loop limit exceeded") { + return false; + } + return true; + }, + }, + } + }, webpack: { alias: { '~': path.resolve(__dirname, 'src'), '@': path.resolve(__dirname, 'src/modules'), '@testing': path.resolve(__dirname, 'src/testing'), - } + }, }, jest: { configure: { diff --git a/front/package.json b/front/package.json index 5d448f44eb..75a30dd48a 100644 --- a/front/package.json +++ b/front/package.json @@ -5,6 +5,8 @@ "dependencies": { "@apollo/client": "^3.7.5", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", + "@blocknote/core": "^0.8.2", + "@blocknote/react": "^0.8.2", "@emotion/react": "^11.10.6", "@emotion/styled": "^11.10.5", "@floating-ui/react": "^0.24.3", diff --git a/front/src/App.tsx b/front/src/App.tsx index 595825395c..d4f190dea9 100644 --- a/front/src/App.tsx +++ b/front/src/App.tsx @@ -17,6 +17,8 @@ import { People } from '~/pages/people/People'; import { SettingsProfile } from '~/pages/settings/SettingsProfile'; import { SettingsWorkspaceMembers } from '~/pages/settings/SettingsWorkspaceMembers'; +import { CompanyShow } from './pages/companies/CompanyShow'; +import { PersonShow } from './pages/people/PersonShow'; import { AppInternalHooks } from './AppInternalHooks'; /** @@ -65,7 +67,13 @@ export function App() { } /> } /> + } /> } /> + } + /> + } /> ; commentThreadTargets?: Maybe>; comments?: Maybe>; createdAt: Scalars['DateTime']; id: Scalars['ID']; + title?: Maybe; updatedAt: Scalars['DateTime']; }; export type CommentThreadCreateInput = { + author: UserCreateNestedOneWithoutCommentThreadInput; + body?: InputMaybe; commentThreadTargets?: InputMaybe; comments?: InputMaybe; createdAt?: InputMaybe; id?: InputMaybe; + title?: InputMaybe; updatedAt?: InputMaybe; }; -export type CommentThreadCreateManyWorkspaceInput = { +export type CommentThreadCreateManyAuthorInput = { + body?: InputMaybe; createdAt?: InputMaybe; id?: InputMaybe; + title?: InputMaybe; + updatedAt?: InputMaybe; +}; + +export type CommentThreadCreateManyAuthorInputEnvelope = { + data: Array; + skipDuplicates?: InputMaybe; +}; + +export type CommentThreadCreateManyWorkspaceInput = { + authorId: Scalars['String']; + body?: InputMaybe; + createdAt?: InputMaybe; + id?: InputMaybe; + title?: InputMaybe; updatedAt?: InputMaybe; }; @@ -232,10 +255,22 @@ export type CommentThreadCreateManyWorkspaceInputEnvelope = { skipDuplicates?: InputMaybe; }; +export type CommentThreadCreateNestedManyWithoutAuthorInput = { + connect?: InputMaybe>; + connectOrCreate?: InputMaybe>; + create?: InputMaybe>; + createMany?: InputMaybe; +}; + export type CommentThreadCreateNestedOneWithoutCommentsInput = { connect?: InputMaybe; }; +export type CommentThreadCreateOrConnectWithoutAuthorInput = { + create: CommentThreadCreateWithoutAuthorInput; + where: CommentThreadWhereUniqueInput; +}; + export type CommentThreadCreateOrConnectWithoutCommentsInput = { create: CommentThreadCreateWithoutCommentsInput; where: CommentThreadWhereUniqueInput; @@ -246,26 +281,56 @@ export type CommentThreadCreateOrConnectWithoutWorkspaceInput = { where: CommentThreadWhereUniqueInput; }; -export type CommentThreadCreateWithoutCommentsInput = { - commentThreadTargets?: InputMaybe; - createdAt?: InputMaybe; - id?: InputMaybe; - updatedAt?: InputMaybe; -}; - -export type CommentThreadCreateWithoutWorkspaceInput = { +export type CommentThreadCreateWithoutAuthorInput = { + body?: InputMaybe; commentThreadTargets?: InputMaybe; comments?: InputMaybe; createdAt?: InputMaybe; id?: InputMaybe; + title?: InputMaybe; updatedAt?: InputMaybe; }; +export type CommentThreadCreateWithoutCommentsInput = { + author: UserCreateNestedOneWithoutCommentThreadInput; + body?: InputMaybe; + commentThreadTargets?: InputMaybe; + createdAt?: InputMaybe; + id?: InputMaybe; + title?: InputMaybe; + updatedAt?: InputMaybe; +}; + +export type CommentThreadCreateWithoutWorkspaceInput = { + author: UserCreateNestedOneWithoutCommentThreadInput; + body?: InputMaybe; + commentThreadTargets?: InputMaybe; + comments?: InputMaybe; + createdAt?: InputMaybe; + id?: InputMaybe; + title?: InputMaybe; + updatedAt?: InputMaybe; +}; + +export type CommentThreadListRelationFilter = { + every?: InputMaybe; + none?: InputMaybe; + some?: InputMaybe; +}; + +export type CommentThreadOrderByRelationAggregateInput = { + _count?: InputMaybe; +}; + export type CommentThreadOrderByWithRelationInput = { + author?: InputMaybe; + authorId?: InputMaybe; + body?: InputMaybe; commentThreadTargets?: InputMaybe; comments?: InputMaybe; createdAt?: InputMaybe; id?: InputMaybe; + title?: InputMaybe; updatedAt?: InputMaybe; }; @@ -275,9 +340,12 @@ export type CommentThreadRelationFilter = { }; export enum CommentThreadScalarFieldEnum { + AuthorId = 'authorId', + Body = 'body', CreatedAt = 'createdAt', DeletedAt = 'deletedAt', Id = 'id', + Title = 'title', UpdatedAt = 'updatedAt', WorkspaceId = 'workspaceId' } @@ -286,8 +354,11 @@ export type CommentThreadScalarWhereInput = { AND?: InputMaybe>; NOT?: InputMaybe>; OR?: InputMaybe>; + authorId?: InputMaybe; + body?: InputMaybe; createdAt?: InputMaybe; id?: InputMaybe; + title?: InputMaybe; updatedAt?: InputMaybe; }; @@ -418,24 +489,48 @@ export type CommentThreadTargetWhereUniqueInput = { }; export type CommentThreadUpdateInput = { + author?: InputMaybe; + body?: InputMaybe; commentThreadTargets?: InputMaybe; comments?: InputMaybe; createdAt?: InputMaybe; id?: InputMaybe; + title?: InputMaybe; updatedAt?: InputMaybe; }; export type CommentThreadUpdateManyMutationInput = { + body?: InputMaybe; createdAt?: InputMaybe; id?: InputMaybe; + title?: InputMaybe; updatedAt?: InputMaybe; }; +export type CommentThreadUpdateManyWithWhereWithoutAuthorInput = { + data: CommentThreadUpdateManyMutationInput; + where: CommentThreadScalarWhereInput; +}; + export type CommentThreadUpdateManyWithWhereWithoutWorkspaceInput = { data: CommentThreadUpdateManyMutationInput; where: CommentThreadScalarWhereInput; }; +export type CommentThreadUpdateManyWithoutAuthorNestedInput = { + connect?: InputMaybe>; + connectOrCreate?: InputMaybe>; + create?: InputMaybe>; + createMany?: InputMaybe; + delete?: InputMaybe>; + deleteMany?: InputMaybe>; + disconnect?: InputMaybe>; + set?: InputMaybe>; + update?: InputMaybe>; + updateMany?: InputMaybe>; + upsert?: InputMaybe>; +}; + export type CommentThreadUpdateManyWithoutWorkspaceNestedInput = { connect?: InputMaybe>; connectOrCreate?: InputMaybe>; @@ -458,26 +553,53 @@ export type CommentThreadUpdateOneRequiredWithoutCommentsNestedInput = { upsert?: InputMaybe; }; +export type CommentThreadUpdateWithWhereUniqueWithoutAuthorInput = { + data: CommentThreadUpdateWithoutAuthorInput; + where: CommentThreadWhereUniqueInput; +}; + export type CommentThreadUpdateWithWhereUniqueWithoutWorkspaceInput = { data: CommentThreadUpdateWithoutWorkspaceInput; where: CommentThreadWhereUniqueInput; }; -export type CommentThreadUpdateWithoutCommentsInput = { - commentThreadTargets?: InputMaybe; - createdAt?: InputMaybe; - id?: InputMaybe; - updatedAt?: InputMaybe; -}; - -export type CommentThreadUpdateWithoutWorkspaceInput = { +export type CommentThreadUpdateWithoutAuthorInput = { + body?: InputMaybe; commentThreadTargets?: InputMaybe; comments?: InputMaybe; createdAt?: InputMaybe; id?: InputMaybe; + title?: InputMaybe; updatedAt?: InputMaybe; }; +export type CommentThreadUpdateWithoutCommentsInput = { + author?: InputMaybe; + body?: InputMaybe; + commentThreadTargets?: InputMaybe; + createdAt?: InputMaybe; + id?: InputMaybe; + title?: InputMaybe; + updatedAt?: InputMaybe; +}; + +export type CommentThreadUpdateWithoutWorkspaceInput = { + author?: InputMaybe; + body?: InputMaybe; + commentThreadTargets?: InputMaybe; + comments?: InputMaybe; + createdAt?: InputMaybe; + id?: InputMaybe; + title?: InputMaybe; + updatedAt?: InputMaybe; +}; + +export type CommentThreadUpsertWithWhereUniqueWithoutAuthorInput = { + create: CommentThreadCreateWithoutAuthorInput; + update: CommentThreadUpdateWithoutAuthorInput; + where: CommentThreadWhereUniqueInput; +}; + export type CommentThreadUpsertWithWhereUniqueWithoutWorkspaceInput = { create: CommentThreadCreateWithoutWorkspaceInput; update: CommentThreadUpdateWithoutWorkspaceInput; @@ -493,10 +615,14 @@ export type CommentThreadWhereInput = { AND?: InputMaybe>; NOT?: InputMaybe>; OR?: InputMaybe>; + author?: InputMaybe; + authorId?: InputMaybe; + body?: InputMaybe; commentThreadTargets?: InputMaybe; comments?: InputMaybe; createdAt?: InputMaybe; id?: InputMaybe; + title?: InputMaybe; updatedAt?: InputMaybe; }; @@ -651,7 +777,7 @@ export enum CommentableType { export type Company = { __typename?: 'Company'; - _commentCount: Scalars['Int']; + _commentThreadCount: Scalars['Int']; accountOwner?: Maybe; accountOwnerId?: Maybe; address: Scalars['String']; @@ -1270,13 +1396,14 @@ export type NullableStringFieldUpdateOperationsInput = { export type Person = { __typename?: 'Person'; - _commentCount: Scalars['Int']; + _commentThreadCount: Scalars['Int']; city: Scalars['String']; commentThreads: Array; comments: Array; company?: Maybe; companyId?: Maybe; createdAt: Scalars['DateTime']; + displayName: Scalars['String']; email: Scalars['String']; firstName: Scalars['String']; id: Scalars['ID']; @@ -2303,6 +2430,8 @@ export type Query = { findManyPipelineProgress: Array; findManyPipelineStage: Array; findManyUser: Array; + findUniqueCompany: Company; + findUniquePerson: Person; }; @@ -2380,6 +2509,16 @@ export type QueryFindManyUserArgs = { where?: InputMaybe; }; + +export type QueryFindUniqueCompanyArgs = { + id: Scalars['String']; +}; + + +export type QueryFindUniquePersonArgs = { + id: Scalars['String']; +}; + export enum QueryMode { Default = 'default', Insensitive = 'insensitive' @@ -2432,6 +2571,7 @@ export type Telemetry = { export type User = { __typename?: 'User'; + CommentThread?: Maybe>; avatarUrl?: Maybe; comments?: Maybe>; companies?: Maybe>; @@ -2451,6 +2591,12 @@ export type User = { workspaceMember?: Maybe; }; +export type UserCreateNestedOneWithoutCommentThreadInput = { + connect?: InputMaybe; + connectOrCreate?: InputMaybe; + create?: InputMaybe; +}; + export type UserCreateNestedOneWithoutCommentsInput = { connect?: InputMaybe; }; @@ -2465,6 +2611,11 @@ export type UserCreateNestedOneWithoutWorkspaceMemberInput = { create?: InputMaybe; }; +export type UserCreateOrConnectWithoutCommentThreadInput = { + create: UserCreateWithoutCommentThreadInput; + where: UserWhereUniqueInput; +}; + export type UserCreateOrConnectWithoutCommentsInput = { create: UserCreateWithoutCommentsInput; where: UserWhereUniqueInput; @@ -2475,7 +2626,26 @@ export type UserCreateOrConnectWithoutWorkspaceMemberInput = { where: UserWhereUniqueInput; }; +export type UserCreateWithoutCommentThreadInput = { + avatarUrl?: InputMaybe; + comments?: InputMaybe; + companies?: InputMaybe; + createdAt?: InputMaybe; + disabled?: InputMaybe; + email: Scalars['String']; + emailVerified?: InputMaybe; + firstName?: InputMaybe; + id?: InputMaybe; + lastName?: InputMaybe; + lastSeen?: InputMaybe; + locale: Scalars['String']; + metadata?: InputMaybe; + phoneNumber?: InputMaybe; + updatedAt?: InputMaybe; +}; + export type UserCreateWithoutCommentsInput = { + CommentThread?: InputMaybe; avatarUrl?: InputMaybe; companies?: InputMaybe; createdAt?: InputMaybe; @@ -2493,6 +2663,7 @@ export type UserCreateWithoutCommentsInput = { }; export type UserCreateWithoutWorkspaceMemberInput = { + CommentThread?: InputMaybe; avatarUrl?: InputMaybe; comments?: InputMaybe; companies?: InputMaybe; @@ -2516,6 +2687,7 @@ export type UserExists = { }; export type UserOrderByWithRelationInput = { + CommentThread?: InputMaybe; avatarUrl?: InputMaybe; comments?: InputMaybe; companies?: InputMaybe; @@ -2557,6 +2729,7 @@ export enum UserScalarFieldEnum { } export type UserUpdateInput = { + CommentThread?: InputMaybe; avatarUrl?: InputMaybe; comments?: InputMaybe; companies?: InputMaybe; @@ -2574,6 +2747,14 @@ export type UserUpdateInput = { updatedAt?: InputMaybe; }; +export type UserUpdateOneRequiredWithoutCommentThreadNestedInput = { + connect?: InputMaybe; + connectOrCreate?: InputMaybe; + create?: InputMaybe; + update?: InputMaybe; + upsert?: InputMaybe; +}; + export type UserUpdateOneRequiredWithoutCommentsNestedInput = { connect?: InputMaybe; connectOrCreate?: InputMaybe; @@ -2594,24 +2775,7 @@ export type UserUpdateOneWithoutCompaniesNestedInput = { connect?: InputMaybe; }; -export type UserUpdateWithoutCommentsInput = { - avatarUrl?: InputMaybe; - companies?: InputMaybe; - createdAt?: InputMaybe; - disabled?: InputMaybe; - email?: InputMaybe; - emailVerified?: InputMaybe; - firstName?: InputMaybe; - id?: InputMaybe; - lastName?: InputMaybe; - lastSeen?: InputMaybe; - locale?: InputMaybe; - metadata?: InputMaybe; - phoneNumber?: InputMaybe; - updatedAt?: InputMaybe; -}; - -export type UserUpdateWithoutWorkspaceMemberInput = { +export type UserUpdateWithoutCommentThreadInput = { avatarUrl?: InputMaybe; comments?: InputMaybe; companies?: InputMaybe; @@ -2629,6 +2793,48 @@ export type UserUpdateWithoutWorkspaceMemberInput = { updatedAt?: InputMaybe; }; +export type UserUpdateWithoutCommentsInput = { + CommentThread?: InputMaybe; + avatarUrl?: InputMaybe; + companies?: InputMaybe; + createdAt?: InputMaybe; + disabled?: InputMaybe; + email?: InputMaybe; + emailVerified?: InputMaybe; + firstName?: InputMaybe; + id?: InputMaybe; + lastName?: InputMaybe; + lastSeen?: InputMaybe; + locale?: InputMaybe; + metadata?: InputMaybe; + phoneNumber?: InputMaybe; + updatedAt?: InputMaybe; +}; + +export type UserUpdateWithoutWorkspaceMemberInput = { + CommentThread?: InputMaybe; + avatarUrl?: InputMaybe; + comments?: InputMaybe; + companies?: InputMaybe; + createdAt?: InputMaybe; + disabled?: InputMaybe; + email?: InputMaybe; + emailVerified?: InputMaybe; + firstName?: InputMaybe; + id?: InputMaybe; + lastName?: InputMaybe; + lastSeen?: InputMaybe; + locale?: InputMaybe; + metadata?: InputMaybe; + phoneNumber?: InputMaybe; + updatedAt?: InputMaybe; +}; + +export type UserUpsertWithoutCommentThreadInput = { + create: UserCreateWithoutCommentThreadInput; + update: UserUpdateWithoutCommentThreadInput; +}; + export type UserUpsertWithoutCommentsInput = { create: UserCreateWithoutCommentsInput; update: UserUpdateWithoutCommentsInput; @@ -2641,6 +2847,7 @@ export type UserUpsertWithoutWorkspaceMemberInput = { export type UserWhereInput = { AND?: InputMaybe>; + CommentThread?: InputMaybe; NOT?: InputMaybe>; OR?: InputMaybe>; avatarUrl?: InputMaybe; @@ -2851,17 +3058,17 @@ export type CreateCommentMutationVariables = Exact<{ export type CreateCommentMutation = { __typename?: 'Mutation', createOneComment: { __typename?: 'Comment', id: string, createdAt: string, body: string, commentThreadId: string, author: { __typename?: 'User', id: string, displayName: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null } } }; -export type CreateCommentThreadWithCommentMutationVariables = Exact<{ +export type CreateCommentThreadMutationVariables = Exact<{ commentThreadId: Scalars['String']; - commentText: Scalars['String']; + body?: InputMaybe; + title?: InputMaybe; authorId: Scalars['String']; createdAt: Scalars['DateTime']; - commentId: Scalars['String']; commentThreadTargetArray: Array | CommentThreadTargetCreateManyCommentThreadInput; }>; -export type CreateCommentThreadWithCommentMutation = { __typename?: 'Mutation', createOneCommentThread: { __typename?: 'CommentThread', id: string, createdAt: string, updatedAt: string, commentThreadTargets?: Array<{ __typename?: 'CommentThreadTarget', id: string, createdAt: string, updatedAt: string, commentThreadId: string, commentableType: CommentableType, commentableId: string }> | null, comments?: Array<{ __typename?: 'Comment', id: string, createdAt: string, updatedAt: string, body: string, author: { __typename?: 'User', id: string } }> | null } }; +export type CreateCommentThreadMutation = { __typename?: 'Mutation', createOneCommentThread: { __typename?: 'CommentThread', id: string, createdAt: string, updatedAt: string, authorId: string, commentThreadTargets?: Array<{ __typename?: 'CommentThreadTarget', id: string, createdAt: string, updatedAt: string, commentThreadId: string, commentableType: CommentableType, commentableId: string }> | null, comments?: Array<{ __typename?: 'Comment', id: string, createdAt: string, updatedAt: string, body: string, author: { __typename?: 'User', id: string } }> | null } }; export type GetCommentThreadsByTargetsQueryVariables = Exact<{ commentThreadTargetIds: Array | Scalars['String']; @@ -2869,14 +3076,14 @@ export type GetCommentThreadsByTargetsQueryVariables = Exact<{ }>; -export type GetCommentThreadsByTargetsQuery = { __typename?: 'Query', findManyCommentThreads: Array<{ __typename?: 'CommentThread', id: 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, commentThreadTargets?: Array<{ __typename?: 'CommentThreadTarget', id: string, commentableId: string, commentableType: CommentableType }> | null }> }; +export type GetCommentThreadsByTargetsQuery = { __typename?: 'Query', findManyCommentThreads: Array<{ __typename?: 'CommentThread', id: string, createdAt: string, title?: string | null, body?: string | null, author: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null }, 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, commentThreadTargets?: Array<{ __typename?: 'CommentThreadTarget', id: string, commentableId: string, commentableType: CommentableType }> | null }> }; export type GetCommentThreadQueryVariables = Exact<{ commentThreadId: Scalars['String']; }>; -export type GetCommentThreadQuery = { __typename?: 'Query', findManyCommentThreads: Array<{ __typename?: 'CommentThread', id: 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, commentThreadTargets?: Array<{ __typename?: 'CommentThreadTarget', commentableId: string, commentableType: CommentableType }> | null }> }; +export type GetCommentThreadQuery = { __typename?: 'Query', findManyCommentThreads: Array<{ __typename?: 'CommentThread', id: string, createdAt: string, body?: string | null, title?: string | null, author: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null }, 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, commentThreadTargets?: Array<{ __typename?: 'CommentThreadTarget', id: string, commentableId: string, commentableType: CommentableType }> | null }> }; export type AddCommentThreadTargetOnCommentThreadMutationVariables = Exact<{ commentThreadId: Scalars['String']; @@ -2904,13 +3111,36 @@ export type DeleteCommentThreadMutationVariables = Exact<{ export type DeleteCommentThreadMutation = { __typename?: 'Mutation', deleteManyCommentThreads: { __typename?: 'AffectedRows', count: number } }; +export type UpdateCommentThreadTitleMutationVariables = Exact<{ + commentThreadId: Scalars['String']; + commentThreadTitle?: InputMaybe; +}>; + + +export type UpdateCommentThreadTitleMutation = { __typename?: 'Mutation', updateOneCommentThread: { __typename?: 'CommentThread', id: string, title?: string | null } }; + +export type UpdateCommentThreadBodyMutationVariables = Exact<{ + commentThreadId: Scalars['String']; + commentThreadBody?: InputMaybe; +}>; + + +export type UpdateCommentThreadBodyMutation = { __typename?: 'Mutation', updateOneCommentThread: { __typename?: 'CommentThread', id: string, body?: string | null } }; + export type GetCompaniesQueryVariables = Exact<{ orderBy?: InputMaybe | CompanyOrderByWithRelationInput>; where?: InputMaybe; }>; -export type GetCompaniesQuery = { __typename?: 'Query', companies: Array<{ __typename?: 'Company', id: string, domainName: string, name: string, createdAt: string, address: string, employees?: number | null, _commentCount: number, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null } | null }> }; +export type GetCompaniesQuery = { __typename?: 'Query', companies: Array<{ __typename?: 'Company', id: string, domainName: string, name: string, createdAt: string, address: string, employees?: number | null, _commentThreadCount: number, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string, firstName?: string | null, lastName?: string | null } | null }> }; + +export type GetCompanyQueryVariables = Exact<{ + id: Scalars['String']; +}>; + + +export type GetCompanyQuery = { __typename?: 'Query', findUniqueCompany: { __typename?: 'Company', id: string, domainName: string, name: string, createdAt: string, address: string, employees?: number | null, _commentThreadCount: number, accountOwner?: { __typename?: 'User', id: string, email: string, displayName: string } | null } }; export type UpdateCompanyMutationVariables = Exact<{ id?: InputMaybe; @@ -2951,7 +3181,14 @@ export type GetPeopleQueryVariables = Exact<{ }>; -export type GetPeopleQuery = { __typename?: 'Query', people: Array<{ __typename?: 'Person', id: string, phone: string, email: string, city: string, firstName: string, lastName: string, createdAt: string, _commentCount: number, company?: { __typename?: 'Company', id: string, name: string, domainName: string } | null }> }; +export type GetPeopleQuery = { __typename?: 'Query', people: Array<{ __typename?: 'Person', id: string, phone: string, email: string, city: string, firstName: string, lastName: string, createdAt: string, _commentThreadCount: number, company?: { __typename?: 'Company', id: string, name: string, domainName: string } | null }> }; + +export type GetPersonQueryVariables = Exact<{ + id: Scalars['String']; +}>; + + +export type GetPersonQuery = { __typename?: 'Query', findUniquePerson: { __typename?: 'Person', id: string, firstName: string, lastName: string, displayName: string, createdAt: string } }; export type UpdatePeopleMutationVariables = Exact<{ id?: InputMaybe; @@ -3416,14 +3653,15 @@ export function useCreateCommentMutation(baseOptions?: Apollo.MutationHookOption export type CreateCommentMutationHookResult = ReturnType; export type CreateCommentMutationResult = Apollo.MutationResult; export type CreateCommentMutationOptions = Apollo.BaseMutationOptions; -export const CreateCommentThreadWithCommentDocument = gql` - mutation CreateCommentThreadWithComment($commentThreadId: String!, $commentText: String!, $authorId: String!, $createdAt: DateTime!, $commentId: String!, $commentThreadTargetArray: [CommentThreadTargetCreateManyCommentThreadInput!]!) { +export const CreateCommentThreadDocument = gql` + mutation CreateCommentThread($commentThreadId: String!, $body: String, $title: String, $authorId: String!, $createdAt: DateTime!, $commentThreadTargetArray: [CommentThreadTargetCreateManyCommentThreadInput!]!) { createOneCommentThread( - data: {id: $commentThreadId, createdAt: $createdAt, updatedAt: $createdAt, comments: {createMany: {data: {authorId: $authorId, id: $commentId, createdAt: $createdAt, body: $commentText}}}, commentThreadTargets: {createMany: {data: $commentThreadTargetArray, skipDuplicates: true}}} + data: {id: $commentThreadId, createdAt: $createdAt, updatedAt: $createdAt, author: {connect: {id: $authorId}}, body: $body, title: $title, commentThreadTargets: {createMany: {data: $commentThreadTargetArray, skipDuplicates: true}}} ) { id createdAt updatedAt + authorId commentThreadTargets { id createdAt @@ -3444,37 +3682,37 @@ export const CreateCommentThreadWithCommentDocument = gql` } } `; -export type CreateCommentThreadWithCommentMutationFn = Apollo.MutationFunction; +export type CreateCommentThreadMutationFn = Apollo.MutationFunction; /** - * __useCreateCommentThreadWithCommentMutation__ + * __useCreateCommentThreadMutation__ * - * To run a mutation, you first call `useCreateCommentThreadWithCommentMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useCreateCommentThreadWithCommentMutation` returns a tuple that includes: + * To run a mutation, you first call `useCreateCommentThreadMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useCreateCommentThreadMutation` 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 [createCommentThreadWithCommentMutation, { data, loading, error }] = useCreateCommentThreadWithCommentMutation({ + * const [createCommentThreadMutation, { data, loading, error }] = useCreateCommentThreadMutation({ * variables: { * commentThreadId: // value for 'commentThreadId' - * commentText: // value for 'commentText' + * body: // value for 'body' + * title: // value for 'title' * authorId: // value for 'authorId' * createdAt: // value for 'createdAt' - * commentId: // value for 'commentId' * commentThreadTargetArray: // value for 'commentThreadTargetArray' * }, * }); */ -export function useCreateCommentThreadWithCommentMutation(baseOptions?: Apollo.MutationHookOptions) { +export function useCreateCommentThreadMutation(baseOptions?: Apollo.MutationHookOptions) { const options = {...defaultOptions, ...baseOptions} - return Apollo.useMutation(CreateCommentThreadWithCommentDocument, options); + return Apollo.useMutation(CreateCommentThreadDocument, options); } -export type CreateCommentThreadWithCommentMutationHookResult = ReturnType; -export type CreateCommentThreadWithCommentMutationResult = Apollo.MutationResult; -export type CreateCommentThreadWithCommentMutationOptions = Apollo.BaseMutationOptions; +export type CreateCommentThreadMutationHookResult = ReturnType; +export type CreateCommentThreadMutationResult = Apollo.MutationResult; +export type CreateCommentThreadMutationOptions = Apollo.BaseMutationOptions; export const GetCommentThreadsByTargetsDocument = gql` query GetCommentThreadsByTargets($commentThreadTargetIds: [String!]!, $orderBy: [CommentThreadOrderByWithRelationInput!]) { findManyCommentThreads( @@ -3482,6 +3720,14 @@ export const GetCommentThreadsByTargetsDocument = gql` where: {commentThreadTargets: {some: {commentableId: {in: $commentThreadTargetIds}}}} ) { id + createdAt + title + body + author { + id + firstName + lastName + } comments { id body @@ -3536,6 +3782,14 @@ export const GetCommentThreadDocument = gql` query GetCommentThread($commentThreadId: String!) { findManyCommentThreads(where: {id: {equals: $commentThreadId}}) { id + createdAt + body + title + author { + id + firstName + lastName + } comments { id body @@ -3550,6 +3804,7 @@ export const GetCommentThreadDocument = gql` } } commentThreadTargets { + id commentableId commentableType } @@ -3712,6 +3967,82 @@ export function useDeleteCommentThreadMutation(baseOptions?: Apollo.MutationHook export type DeleteCommentThreadMutationHookResult = ReturnType; export type DeleteCommentThreadMutationResult = Apollo.MutationResult; export type DeleteCommentThreadMutationOptions = Apollo.BaseMutationOptions; +export const UpdateCommentThreadTitleDocument = gql` + mutation UpdateCommentThreadTitle($commentThreadId: String!, $commentThreadTitle: String) { + updateOneCommentThread( + where: {id: $commentThreadId} + data: {title: {set: $commentThreadTitle}} + ) { + id + title + } +} + `; +export type UpdateCommentThreadTitleMutationFn = Apollo.MutationFunction; + +/** + * __useUpdateCommentThreadTitleMutation__ + * + * To run a mutation, you first call `useUpdateCommentThreadTitleMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useUpdateCommentThreadTitleMutation` 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 [updateCommentThreadTitleMutation, { data, loading, error }] = useUpdateCommentThreadTitleMutation({ + * variables: { + * commentThreadId: // value for 'commentThreadId' + * commentThreadTitle: // value for 'commentThreadTitle' + * }, + * }); + */ +export function useUpdateCommentThreadTitleMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(UpdateCommentThreadTitleDocument, options); + } +export type UpdateCommentThreadTitleMutationHookResult = ReturnType; +export type UpdateCommentThreadTitleMutationResult = Apollo.MutationResult; +export type UpdateCommentThreadTitleMutationOptions = Apollo.BaseMutationOptions; +export const UpdateCommentThreadBodyDocument = gql` + mutation UpdateCommentThreadBody($commentThreadId: String!, $commentThreadBody: String) { + updateOneCommentThread( + where: {id: $commentThreadId} + data: {body: {set: $commentThreadBody}} + ) { + id + body + } +} + `; +export type UpdateCommentThreadBodyMutationFn = Apollo.MutationFunction; + +/** + * __useUpdateCommentThreadBodyMutation__ + * + * To run a mutation, you first call `useUpdateCommentThreadBodyMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useUpdateCommentThreadBodyMutation` 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 [updateCommentThreadBodyMutation, { data, loading, error }] = useUpdateCommentThreadBodyMutation({ + * variables: { + * commentThreadId: // value for 'commentThreadId' + * commentThreadBody: // value for 'commentThreadBody' + * }, + * }); + */ +export function useUpdateCommentThreadBodyMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(UpdateCommentThreadBodyDocument, options); + } +export type UpdateCommentThreadBodyMutationHookResult = ReturnType; +export type UpdateCommentThreadBodyMutationResult = Apollo.MutationResult; +export type UpdateCommentThreadBodyMutationOptions = Apollo.BaseMutationOptions; export const GetCompaniesDocument = gql` query GetCompanies($orderBy: [CompanyOrderByWithRelationInput!], $where: CompanyWhereInput) { companies: findManyCompany(orderBy: $orderBy, where: $where) { @@ -3721,7 +4052,7 @@ export const GetCompaniesDocument = gql` createdAt address employees - _commentCount + _commentThreadCount accountOwner { id email @@ -3761,6 +4092,52 @@ export function useGetCompaniesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptio export type GetCompaniesQueryHookResult = ReturnType; export type GetCompaniesLazyQueryHookResult = ReturnType; export type GetCompaniesQueryResult = Apollo.QueryResult; +export const GetCompanyDocument = gql` + query GetCompany($id: String!) { + findUniqueCompany(id: $id) { + id + domainName + name + createdAt + address + employees + _commentThreadCount + accountOwner { + id + email + displayName + } + } +} + `; + +/** + * __useGetCompanyQuery__ + * + * To run a query within a React component, call `useGetCompanyQuery` and pass it any options that fit your needs. + * When your component renders, `useGetCompanyQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetCompanyQuery({ + * variables: { + * id: // value for 'id' + * }, + * }); + */ +export function useGetCompanyQuery(baseOptions: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(GetCompanyDocument, options); + } +export function useGetCompanyLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(GetCompanyDocument, options); + } +export type GetCompanyQueryHookResult = ReturnType; +export type GetCompanyLazyQueryHookResult = ReturnType; +export type GetCompanyQueryResult = Apollo.QueryResult; export const UpdateCompanyDocument = gql` mutation UpdateCompany($id: String, $name: String, $domainName: String, $accountOwnerId: String, $createdAt: DateTime, $address: String, $employees: Int) { updateOneCompany( @@ -3903,7 +4280,7 @@ export const GetPeopleDocument = gql` firstName lastName createdAt - _commentCount + _commentThreadCount company { id name @@ -3942,6 +4319,45 @@ export function useGetPeopleLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions< export type GetPeopleQueryHookResult = ReturnType; export type GetPeopleLazyQueryHookResult = ReturnType; export type GetPeopleQueryResult = Apollo.QueryResult; +export const GetPersonDocument = gql` + query GetPerson($id: String!) { + findUniquePerson(id: $id) { + id + firstName + lastName + displayName + createdAt + } +} + `; + +/** + * __useGetPersonQuery__ + * + * To run a query within a React component, call `useGetPersonQuery` and pass it any options that fit your needs. + * When your component renders, `useGetPersonQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetPersonQuery({ + * variables: { + * id: // value for 'id' + * }, + * }); + */ +export function useGetPersonQuery(baseOptions: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(GetPersonDocument, options); + } +export function useGetPersonLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(GetPersonDocument, options); + } +export type GetPersonQueryHookResult = ReturnType; +export type GetPersonLazyQueryHookResult = ReturnType; +export type GetPersonQueryResult = Apollo.QueryResult; export const UpdatePeopleDocument = gql` mutation UpdatePeople($id: String, $firstName: String, $lastName: String, $phone: String, $city: String, $companyId: String, $email: String, $createdAt: DateTime) { updateOnePerson( diff --git a/front/src/modules/auth/components/ui/FooterNote.tsx b/front/src/modules/auth/components/ui/FooterNote.tsx index a5afe3d362..5ba151fec6 100644 --- a/front/src/modules/auth/components/ui/FooterNote.tsx +++ b/front/src/modules/auth/components/ui/FooterNote.tsx @@ -7,7 +7,7 @@ const StyledContainer = styled.div` align-items: center; color: ${({ theme }) => theme.font.color.tertiary}; display: flex; - font-size: ${({ theme }) => theme.font.size.sm}px; + font-size: ${({ theme }) => theme.font.size.sm}; text-align: center; `; diff --git a/front/src/modules/command-menu/components/CommandMenuStyles.tsx b/front/src/modules/command-menu/components/CommandMenuStyles.tsx index e2d3026eb6..89ed893831 100644 --- a/front/src/modules/command-menu/components/CommandMenuStyles.tsx +++ b/front/src/modules/command-menu/components/CommandMenuStyles.tsx @@ -15,6 +15,7 @@ export const StyledDialog = styled(Command.Dialog)` top: 50%; transform: translate(-50%, -50%); width: 100%; + z-index: 1000; `; export const StyledInput = styled(Command.Input)` diff --git a/front/src/modules/comments/components/CommentThread.tsx b/front/src/modules/comments/components/CommentThread.tsx deleted file mode 100644 index adc02a01f4..0000000000 --- a/front/src/modules/comments/components/CommentThread.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { getOperationName } from '@apollo/client/utilities'; -import styled from '@emotion/styled'; -import { useRecoilValue } from 'recoil'; -import { v4 } from 'uuid'; - -import { currentUserState } from '@/auth/states/currentUserState'; -import { CommentThreadForDrawer } from '@/comments/types/CommentThreadForDrawer'; -import { GET_COMPANIES } from '@/companies/services'; -import { GET_PEOPLE } from '@/people/services'; -import { AutosizeTextInput } from '@/ui/components/inputs/AutosizeTextInput'; -import { logError } from '@/utils/logs/logError'; -import { isDefined } from '@/utils/type-guards/isDefined'; -import { isNonEmptyString } from '@/utils/type-guards/isNonEmptyString'; -import { useCreateCommentMutation } from '~/generated/graphql'; - -import { GET_COMMENT_THREADS_BY_TARGETS } from '../services'; - -import { CommentThreadActionBar } from './CommentThreadActionBar'; -import { CommentThreadItem } from './CommentThreadItem'; -import { CommentThreadRelationPicker } from './CommentThreadRelationPicker'; - -type OwnProps = { - commentThread: CommentThreadForDrawer; -}; - -const StyledContainer = styled.div` - align-items: flex-start; - display: flex; - - flex-direction: column; - - gap: ${({ theme }) => theme.spacing(4)}; - - justify-content: flex-start; - padding: ${({ theme }) => theme.spacing(2)}; -`; - -const StyledThreadItemListContainer = styled.div` - align-items: flex-start; - display: flex; - - flex-direction: column; - gap: ${({ theme }) => theme.spacing(4)}; - - justify-content: flex-start; - - width: 100%; -`; - -export function CommentThread({ commentThread }: OwnProps) { - const [createCommentMutation] = useCreateCommentMutation(); - const currentUser = useRecoilValue(currentUserState); - - function handleSendComment(commentText: string) { - if (!isNonEmptyString(commentText)) { - return; - } - - if (!isDefined(currentUser)) { - logError( - 'In handleSendComment, currentUser is not defined, this should not happen.', - ); - return; - } - - createCommentMutation({ - variables: { - commentId: v4(), - authorId: currentUser.id, - commentThreadId: commentThread.id, - commentText, - createdAt: new Date().toISOString(), - }, - refetchQueries: [ - getOperationName(GET_COMPANIES) ?? '', - getOperationName(GET_PEOPLE) ?? '', - getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '', - ], - onError: (error) => { - logError( - `In handleSendComment, createCommentMutation onError, error: ${error}`, - ); - }, - }); - } - - return ( - - - {commentThread.comments?.map((comment, index) => ( - - ) : ( - <> - ) - } - /> - ))} - - - - - ); -} diff --git a/front/src/modules/comments/components/CommentThreadCreateMode.tsx b/front/src/modules/comments/components/CommentThreadCreateMode.tsx deleted file mode 100644 index 98ba4c611a..0000000000 --- a/front/src/modules/comments/components/CommentThreadCreateMode.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import { getOperationName } from '@apollo/client/utilities'; -import styled from '@emotion/styled'; -import { useRecoilState, useRecoilValue } from 'recoil'; -import { v4 } from 'uuid'; - -import { currentUserState } from '@/auth/states/currentUserState'; -import { commentableEntityArrayState } from '@/comments/states/commentableEntityArrayState'; -import { createdCommentThreadIdState } from '@/comments/states/createdCommentThreadIdState'; -import { GET_COMPANIES } from '@/companies/services'; -import { GET_PEOPLE } from '@/people/services'; -import { AutosizeTextInput } from '@/ui/components/inputs/AutosizeTextInput'; -import { useOpenRightDrawer } from '@/ui/layout/right-drawer/hooks/useOpenRightDrawer'; -import { logError } from '@/utils/logs/logError'; -import { isDefined } from '@/utils/type-guards/isDefined'; -import { isNonEmptyString } from '@/utils/type-guards/isNonEmptyString'; -import { - useCreateCommentMutation, - useCreateCommentThreadWithCommentMutation, - useGetCommentThreadQuery, -} from '~/generated/graphql'; - -import { GET_COMMENT_THREAD } from '../services'; - -import { CommentThreadItem } from './CommentThreadItem'; - -const StyledContainer = styled.div` - align-items: flex-start; - display: flex; - flex-direction: column; - gap: ${({ theme }) => theme.spacing(4)}; - - justify-content: flex-start; - - max-height: calc(100% - 16px); - padding: ${({ theme }) => theme.spacing(2)}; -`; - -const StyledThreadItemListContainer = styled.div` - align-items: flex-start; - display: flex; - - flex-direction: column-reverse; - gap: ${({ theme }) => theme.spacing(4)}; - - justify-content: flex-start; - overflow: auto; - - width: 100%; -`; - -export function CommentThreadCreateMode() { - const [commentableEntityArray] = useRecoilState(commentableEntityArrayState); - - const [createdCommmentThreadId, setCreatedCommentThreadId] = useRecoilState( - createdCommentThreadIdState, - ); - - const openRightDrawer = useOpenRightDrawer(); - - const [createCommentMutation] = useCreateCommentMutation(); - - const [createCommentThreadWithComment] = - useCreateCommentThreadWithCommentMutation(); - - const { data } = useGetCommentThreadQuery({ - variables: { - commentThreadId: createdCommmentThreadId ?? '', - }, - skip: !createdCommmentThreadId, - }); - - const comments = data?.findManyCommentThreads[0]?.comments; - - const displayCommentList = (comments?.length ?? 0) > 0; - - const currentUser = useRecoilValue(currentUserState); - - function handleNewComment(commentText: string) { - if (!isNonEmptyString(commentText)) { - return; - } - - if (!isDefined(currentUser)) { - logError( - 'In handleCreateCommentThread, currentUser is not defined, this should not happen.', - ); - return; - } - - if (!createdCommmentThreadId) { - createCommentThreadWithComment({ - variables: { - authorId: currentUser.id, - commentId: v4(), - commentText: commentText, - commentThreadId: v4(), - createdAt: new Date().toISOString(), - commentThreadTargetArray: commentableEntityArray.map( - (commentableEntity) => ({ - commentableId: commentableEntity.id, - commentableType: commentableEntity.type, - id: v4(), - createdAt: new Date().toISOString(), - }), - ), - }, - refetchQueries: [ - getOperationName(GET_COMPANIES) ?? '', - getOperationName(GET_PEOPLE) ?? '', - getOperationName(GET_COMMENT_THREAD) ?? '', - ], - onCompleted(data) { - setCreatedCommentThreadId(data.createOneCommentThread.id); - openRightDrawer('comments'); - }, - }); - } else { - createCommentMutation({ - variables: { - commentId: v4(), - authorId: currentUser.id, - commentThreadId: createdCommmentThreadId, - commentText, - createdAt: new Date().toISOString(), - }, - refetchQueries: [getOperationName(GET_COMMENT_THREAD) ?? ''], - onError: (error) => { - logError( - `In handleCreateCommentThread, createCommentMutation onError, error: ${error}`, - ); - }, - }); - } - } - - return ( - - {displayCommentList && ( - - {comments?.map((comment) => ( - - ))} - - )} - - - ); -} diff --git a/front/src/modules/comments/components/RightDrawerComments.tsx b/front/src/modules/comments/components/RightDrawerComments.tsx deleted file mode 100644 index eab5a97b7b..0000000000 --- a/front/src/modules/comments/components/RightDrawerComments.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { useRecoilState } from 'recoil'; - -import { CommentThreadForDrawer } from '@/comments/types/CommentThreadForDrawer'; -import { useHotkeysScopeOnMountOnly } from '@/hotkeys/hooks/useHotkeysScopeOnMountOnly'; -import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope'; -import { RightDrawerBody } from '@/ui/layout/right-drawer/components/RightDrawerBody'; -import { RightDrawerPage } from '@/ui/layout/right-drawer/components/RightDrawerPage'; -import { RightDrawerTopBar } from '@/ui/layout/right-drawer/components/RightDrawerTopBar'; -import { - SortOrder, - useGetCommentThreadsByTargetsQuery, -} from '~/generated/graphql'; - -import { commentableEntityArrayState } from '../states/commentableEntityArrayState'; - -import { CommentThread } from './CommentThread'; - -export function RightDrawerComments() { - const [commentableEntityArray] = useRecoilState(commentableEntityArrayState); - useHotkeysScopeOnMountOnly({ - scope: InternalHotkeysScope.RightDrawer, - customScopes: { goto: false, 'command-menu': true }, - }); - - const { data: queryResult } = useGetCommentThreadsByTargetsQuery({ - variables: { - commentThreadTargetIds: commentableEntityArray.map( - (commentableEntity) => commentableEntity.id, - ), - orderBy: [ - { - createdAt: SortOrder.Desc, - }, - ], - }, - }); - - const commentThreads: CommentThreadForDrawer[] = - queryResult?.findManyCommentThreads ?? []; - - return ( - - - - {commentThreads.map((commentThread) => ( - - ))} - - - ); -} diff --git a/front/src/modules/comments/components/comment-thread/CommentThreadBodyEditor.tsx b/front/src/modules/comments/components/comment-thread/CommentThreadBodyEditor.tsx new file mode 100644 index 0000000000..4978f619e6 --- /dev/null +++ b/front/src/modules/comments/components/comment-thread/CommentThreadBodyEditor.tsx @@ -0,0 +1,60 @@ +import { useMemo } from 'react'; +import { getOperationName } from '@apollo/client/utilities'; +import { BlockNoteEditor } from '@blocknote/core'; +import { useBlockNote } from '@blocknote/react'; +import styled from '@emotion/styled'; + +import { GET_COMMENT_THREADS_BY_TARGETS } from '@/comments/services'; +import { BlockEditor } from '@/ui/components/editor/BlockEditor'; +import { debounce } from '@/utils/debounce'; +import { + CommentThread, + useUpdateCommentThreadBodyMutation, +} from '~/generated/graphql'; + +const BlockNoteStyledContainer = styled.div` + width: 100%; +`; + +type OwnProps = { + commentThread: Pick; + onChange?: (commentThreadBody: string) => void; +}; + +export function CommentThreadBodyEditor({ commentThread, onChange }: OwnProps) { + const [updateCommentThreadBodyMutation] = + useUpdateCommentThreadBodyMutation(); + + const debounceOnChange = useMemo(() => { + function onInternalChange(commentThreadBody: string) { + onChange?.(commentThreadBody); + updateCommentThreadBodyMutation({ + variables: { + commentThreadId: commentThread.id, + commentThreadBody: commentThreadBody, + }, + refetchQueries: [ + getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '', + ], + }); + } + + return debounce(onInternalChange, 200); + }, [commentThread, updateCommentThreadBodyMutation, onChange]); + + const editor: BlockNoteEditor | null = useBlockNote({ + initialContent: commentThread.body + ? JSON.parse(commentThread.body) + : undefined, + editorDOMAttributes: { class: 'editor-edit-mode' }, + onEditorContentChange: (editor) => { + debounceOnChange(JSON.stringify(editor.topLevelBlocks) ?? ''); + }, + }); + + return ( + + + + ); +} diff --git a/front/src/modules/comments/components/comment-thread/CommentThreadComments.tsx b/front/src/modules/comments/components/comment-thread/CommentThreadComments.tsx new file mode 100644 index 0000000000..e416f85101 --- /dev/null +++ b/front/src/modules/comments/components/comment-thread/CommentThreadComments.tsx @@ -0,0 +1,84 @@ +import { getOperationName } from '@apollo/client/utilities'; +import styled from '@emotion/styled'; +import { useRecoilValue } from 'recoil'; +import { v4 } from 'uuid'; + +import { currentUserState } from '@/auth/states/currentUserState'; +import { GET_COMMENT_THREADS_BY_TARGETS } from '@/comments/services'; +import { CommentForDrawer } from '@/comments/types/CommentForDrawer'; +import { AutosizeTextInput } from '@/ui/components/inputs/AutosizeTextInput'; +import { isNonEmptyString } from '@/utils/type-guards/isNonEmptyString'; +import { CommentThread, useCreateCommentMutation } from '~/generated/graphql'; + +import { CommentThreadItem } from '../comment/CommentThreadItem'; + +type OwnProps = { + commentThread: Pick & { + comments: Array; + }; +}; + +const StyledThreadItemListContainer = styled.div` + align-items: flex-start; + border-top: 1px solid ${({ theme }) => theme.border.color.light}; + + box-sizing: border-box; + display: flex; + flex-direction: column; + + gap: ${({ theme }) => theme.spacing(4)}; + + justify-content: flex-start; + padding: ${({ theme }) => theme.spacing(8)}; + padding-left: ${({ theme }) => theme.spacing(12)}; + width: 100%; +`; + +const StyledCommentActionBar = styled.div` + border-top: 1px solid ${({ theme }) => theme.border.color.light}; + display: flex; + padding: 16px 24px 16px 48px; + width: calc(${({ theme }) => theme.rightDrawerWidth} - 48px - 24px); +`; + +export function CommentThreadComments({ commentThread }: OwnProps) { + const [createCommentMutation] = useCreateCommentMutation(); + const currentUser = useRecoilValue(currentUserState); + + if (!currentUser) { + return <>; + } + + function handleSendComment(commentText: string) { + if (!isNonEmptyString(commentText)) { + return; + } + + createCommentMutation({ + variables: { + commentId: v4(), + authorId: currentUser?.id ?? '', + commentThreadId: commentThread?.id ?? '', + commentText: commentText, + createdAt: new Date().toISOString(), + }, + refetchQueries: [getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? ''], + }); + } + + return ( + <> + {commentThread?.comments.length > 0 && ( + + {commentThread?.comments?.map((comment, index) => ( + + ))} + + )} + + + {currentUser && } + + + ); +} diff --git a/front/src/modules/comments/components/CommentThreadRelationPicker.tsx b/front/src/modules/comments/components/comment-thread/CommentThreadRelationPicker.tsx similarity index 82% rename from front/src/modules/comments/components/CommentThreadRelationPicker.tsx rename to front/src/modules/comments/components/comment-thread/CommentThreadRelationPicker.tsx index 93a32afd2f..3c465d006b 100644 --- a/front/src/modules/comments/components/CommentThreadRelationPicker.tsx +++ b/front/src/modules/comments/components/comment-thread/CommentThreadRelationPicker.tsx @@ -1,5 +1,4 @@ import { useState } from 'react'; -import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { autoUpdate, @@ -8,30 +7,33 @@ import { size, useFloating, } from '@floating-ui/react'; -import { IconArrowUpRight } from '@tabler/icons-react'; -import { CommentThreadForDrawer } from '@/comments/types/CommentThreadForDrawer'; +import { useHandleCheckableCommentThreadTargetChange } from '@/comments/hooks/useHandleCheckableCommentThreadTargetChange'; +import { CommentableEntityForSelect } from '@/comments/types/CommentableEntityForSelect'; import CompanyChip from '@/companies/components/CompanyChip'; import { useScopedHotkeys } from '@/hotkeys/hooks/useScopedHotkeys'; import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope'; import { PersonChip } from '@/people/components/PersonChip'; import { RecoilScope } from '@/recoil-scope/components/RecoilScope'; +import { MultipleEntitySelect } from '@/relation-picker/components/MultipleEntitySelect'; import { useFilteredSearchEntityQuery } from '@/relation-picker/hooks/useFilteredSearchEntityQuery'; import { useListenClickOutsideArrayOfRef } from '@/ui/hooks/useListenClickOutsideArrayOfRef'; import { flatMapAndSortEntityForSelectArrayOfArrayByName } from '@/ui/utils/flatMapAndSortEntityForSelectArrayByName'; import { getLogoUrlFromDomainName } from '@/utils/utils'; import { CommentableType, + CommentThread, + CommentThreadTarget, useSearchCompanyQuery, useSearchPeopleQuery, } from '~/generated/graphql'; -import { MultipleEntitySelect } from '../../relation-picker/components/MultipleEntitySelect'; -import { useHandleCheckableCommentThreadTargetChange } from '../hooks/useHandleCheckableCommentThreadTargetChange'; -import { CommentableEntityForSelect } from '../types/CommentableEntityForSelect'; - type OwnProps = { - commentThread: CommentThreadForDrawer; + commentThread?: Pick & { + commentThreadTargets: Array< + Pick + >; + }; }; const StyledContainer = styled.div` @@ -44,25 +46,6 @@ const StyledContainer = styled.div` width: 100%; `; -const StyledLabelContainer = styled.div` - align-items: center; - display: flex; - flex-direction: row; - - gap: ${({ theme }) => theme.spacing(2)}; - - padding-bottom: ${({ theme }) => theme.spacing(2)}; - padding-top: ${({ theme }) => theme.spacing(2)}; -`; - -const StyledRelationLabel = styled.div` - color: ${({ theme }) => theme.font.color.secondary}; - display: flex; - flex-direction: row; - - user-select: none; -`; - const StyledRelationContainer = styled.div` --horizontal-padding: ${({ theme }) => theme.spacing(1)}; --vertical-padding: ${({ theme }) => theme.spacing(1.5)}; @@ -97,15 +80,12 @@ export function CommentThreadRelationPicker({ commentThread }: OwnProps) { const [isMenuOpen, setIsMenuOpen] = useState(false); const [searchFilter, setSearchFilter] = useState(''); - const theme = useTheme(); - const peopleIds = - commentThread.commentThreadTargets + commentThread?.commentThreadTargets ?.filter((relation) => relation.commentableType === 'Person') .map((relation) => relation.commentableId) ?? []; - const companyIds = - commentThread.commentThreadTargets + commentThread?.commentThreadTargets ?.filter((relation) => relation.commentableType === 'Company') .map((relation) => relation.commentableId) ?? []; @@ -203,10 +183,6 @@ export function CommentThreadRelationPicker({ commentThread }: OwnProps) { return ( - - - Relations - ) : ( - + ), )} diff --git a/front/src/modules/comments/components/comment-thread/CommentThreadTypeDropdown.tsx b/front/src/modules/comments/components/comment-thread/CommentThreadTypeDropdown.tsx new file mode 100644 index 0000000000..38b69a7042 --- /dev/null +++ b/front/src/modules/comments/components/comment-thread/CommentThreadTypeDropdown.tsx @@ -0,0 +1,18 @@ +import { + DropdownButton, + DropdownOptionType, +} from '@/ui/components/buttons/DropdownButton'; +import { IconNotes } from '@/ui/icons/index'; + +export function CommentThreadTypeDropdown() { + const options: DropdownOptionType[] = [ + { label: 'Notes', icon: }, + // { label: 'Call', icon: }, + ]; + + const handleSelect = (selectedOption: DropdownOptionType) => { + // console.log(`You selected: ${selectedOption.label}`); + }; + + return ; +} diff --git a/front/src/modules/comments/components/__stories__/CommentThreadRelationPicker.stories.tsx b/front/src/modules/comments/components/comment-thread/__stories__/CommentThreadRelationPicker.stories.tsx similarity index 78% rename from front/src/modules/comments/components/__stories__/CommentThreadRelationPicker.stories.tsx rename to front/src/modules/comments/components/comment-thread/__stories__/CommentThreadRelationPicker.stories.tsx index 881c5c4a5f..dfb473a7fc 100644 --- a/front/src/modules/comments/components/__stories__/CommentThreadRelationPicker.stories.tsx +++ b/front/src/modules/comments/components/comment-thread/__stories__/CommentThreadRelationPicker.stories.tsx @@ -1,3 +1,4 @@ +import { MemoryRouter } from 'react-router-dom'; import styled from '@emotion/styled'; import type { Meta, StoryObj } from '@storybook/react'; @@ -24,8 +25,10 @@ type Story = StoryObj; export const Default: Story = { render: getRenderWrapperForComponent( - - - , + + + + + , ), }; diff --git a/front/src/modules/comments/components/CommentHeader.tsx b/front/src/modules/comments/components/comment/CommentHeader.tsx similarity index 100% rename from front/src/modules/comments/components/CommentHeader.tsx rename to front/src/modules/comments/components/comment/CommentHeader.tsx diff --git a/front/src/modules/comments/components/CommentThreadItem.tsx b/front/src/modules/comments/components/comment/CommentThreadItem.tsx similarity index 93% rename from front/src/modules/comments/components/CommentThreadItem.tsx rename to front/src/modules/comments/components/comment/CommentThreadItem.tsx index a3c81ebba2..5f9b00d83f 100644 --- a/front/src/modules/comments/components/CommentThreadItem.tsx +++ b/front/src/modules/comments/components/comment/CommentThreadItem.tsx @@ -22,7 +22,7 @@ const StyledCommentBody = styled.div` color: ${({ theme }) => theme.font.color.secondary}; font-size: ${({ theme }) => theme.font.size.md}; - line-height: ${({ theme }) => theme.text.lineHeight}; + line-height: ${({ theme }) => theme.text.lineHeight.md}; overflow-wrap: anywhere; padding-left: 24px; diff --git a/front/src/modules/comments/components/__stories__/CommentHeader.stories.tsx b/front/src/modules/comments/components/comment/__stories__/CommentHeader.stories.tsx similarity index 97% rename from front/src/modules/comments/components/__stories__/CommentHeader.stories.tsx rename to front/src/modules/comments/components/comment/__stories__/CommentHeader.stories.tsx index ce19446b0b..b825b17aad 100644 --- a/front/src/modules/comments/components/__stories__/CommentHeader.stories.tsx +++ b/front/src/modules/comments/components/comment/__stories__/CommentHeader.stories.tsx @@ -6,8 +6,8 @@ import { CommentForDrawer } from '@/comments/types/CommentForDrawer'; import { mockedUsersData } from '~/testing/mock-data/users'; import { getRenderWrapperForComponent } from '~/testing/renderWrappers'; +import { CommentThreadActionBar } from '../../right-drawer/CommentThreadActionBar'; import { CommentHeader } from '../CommentHeader'; -import { CommentThreadActionBar } from '../CommentThreadActionBar'; const meta: Meta = { title: 'Modules/Comments/CommentHeader', diff --git a/front/src/modules/comments/components/right-drawer/CommentThread.tsx b/front/src/modules/comments/components/right-drawer/CommentThread.tsx new file mode 100644 index 0000000000..7a48a11d21 --- /dev/null +++ b/front/src/modules/comments/components/right-drawer/CommentThread.tsx @@ -0,0 +1,164 @@ +import React, { useMemo } from 'react'; +import { getOperationName } from '@apollo/client/utilities'; +import styled from '@emotion/styled'; + +import { GET_COMMENT_THREADS_BY_TARGETS } from '@/comments/services'; +import { PropertyBox } from '@/ui/components/property-box/PropertyBox'; +import { PropertyBoxItem } from '@/ui/components/property-box/PropertyBoxItem'; +import { IconArrowUpRight } from '@/ui/icons/index'; +import { debounce } from '@/utils/debounce'; +import { + useGetCommentThreadQuery, + useUpdateCommentThreadTitleMutation, +} from '~/generated/graphql'; + +import { CommentThreadBodyEditor } from '../comment-thread/CommentThreadBodyEditor'; +import { CommentThreadComments } from '../comment-thread/CommentThreadComments'; +import { CommentThreadRelationPicker } from '../comment-thread/CommentThreadRelationPicker'; +import { CommentThreadTypeDropdown } from '../comment-thread/CommentThreadTypeDropdown'; + +import { CommentThreadActionBar } from './CommentThreadActionBar'; + +import '@blocknote/core/style.css'; + +const StyledContainer = styled.div` + align-items: flex-start; + display: flex; + flex-direction: column; + gap: ${({ theme }) => theme.spacing(4)}; + + justify-content: flex-start; +`; + +const StyledTopContainer = styled.div` + align-items: flex-start; + align-self: stretch; + background: ${({ theme }) => theme.background.secondary}; + border-bottom: 1px solid ${({ theme }) => theme.border.color.medium}; + display: flex; + flex-direction: column; + gap: 24px; + padding: 24px 24px 24px 48px; +`; + +const StyledEditableTitleInput = styled.input` + background: transparent; + + border: none; + color: ${({ theme }) => theme.font.color.primary}; + display: flex; + flex: 1 0 0; + + flex-direction: column; + font-family: Inter; + font-size: ${({ theme }) => theme.font.size.xl}; + font-style: normal; + font-weight: ${({ theme }) => theme.font.weight.semiBold}; + justify-content: center; + + line-height: ${({ theme }) => theme.text.lineHeight.md}; + outline: none; + width: calc(100% - ${({ theme }) => theme.spacing(2)}); + :placeholder { + color: ${({ theme }) => theme.font.color.light}; + } +`; + +const StyledTopActionsContainer = styled.div` + align-items: center; + display: flex; + flex-direction: row; + justify-content: space-between; + width: 100%; +`; + +type OwnProps = { + commentThreadId: string; + showComment?: boolean; +}; + +export function CommentThread({ + commentThreadId, + showComment = true, +}: OwnProps) { + const { data } = useGetCommentThreadQuery({ + variables: { + commentThreadId: commentThreadId ?? '', + }, + skip: !commentThreadId, + }); + + const [updateCommentThreadTitleMutation] = + useUpdateCommentThreadTitleMutation(); + + const debounceUpdateTitle = useMemo(() => { + function updateTitle(title: string) { + updateCommentThreadTitleMutation({ + variables: { + commentThreadId: commentThreadId, + commentThreadTitle: title ?? '', + }, + refetchQueries: [ + getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '', + ], + }); + } + return debounce(updateTitle, 200); + }, [commentThreadId, updateCommentThreadTitleMutation]); + + function updateTitleFromBody(body: string) { + const title = JSON.parse(body)[0]?.content[0]?.text; + if (!commentThread?.title || commentThread?.title === '') { + debounceUpdateTitle(title); + } + } + + const commentThread = data?.findManyCommentThreads[0]; + + if (!commentThread) { + return <>; + } + + return ( + + + + + + + debounceUpdateTitle(event.target.value)} + value={commentThread?.title ?? ''} + /> + + } + value={ + + } + label="Relations" + /> + + + + {showComment && ( + + )} + + ); +} diff --git a/front/src/modules/comments/components/CommentThreadActionBar.tsx b/front/src/modules/comments/components/right-drawer/CommentThreadActionBar.tsx similarity index 95% rename from front/src/modules/comments/components/CommentThreadActionBar.tsx rename to front/src/modules/comments/components/right-drawer/CommentThreadActionBar.tsx index 5c2daf6726..37b1ae0f70 100644 --- a/front/src/modules/comments/components/CommentThreadActionBar.tsx +++ b/front/src/modules/comments/components/right-drawer/CommentThreadActionBar.tsx @@ -3,14 +3,13 @@ import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { useRecoilState } from 'recoil'; +import { GET_COMMENT_THREADS_BY_TARGETS } from '@/comments/services'; import { GET_COMPANIES } from '@/companies/services'; import { GET_PEOPLE } from '@/people/services'; import { IconTrash } from '@/ui/icons'; import { isRightDrawerOpenState } from '@/ui/layout/right-drawer/states/isRightDrawerOpenState'; import { useDeleteCommentThreadMutation } from '~/generated/graphql'; -import { GET_COMMENT_THREADS_BY_TARGETS } from '../services'; - const StyledContainer = styled.div` color: ${({ theme }) => theme.font.color.tertiary}; cursor: pointer; diff --git a/front/src/modules/comments/components/right-drawer/RightDrawerTimeline.tsx b/front/src/modules/comments/components/right-drawer/RightDrawerTimeline.tsx new file mode 100644 index 0000000000..fd6a220cf8 --- /dev/null +++ b/front/src/modules/comments/components/right-drawer/RightDrawerTimeline.tsx @@ -0,0 +1,36 @@ +import { useRecoilState } from 'recoil'; + +import { commentableEntityArrayState } from '@/comments/states/commentableEntityArrayState'; +import { useHotkeysScopeOnMountOnly } from '@/hotkeys/hooks/useHotkeysScopeOnMountOnly'; +import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope'; +import { RightDrawerBody } from '@/ui/layout/right-drawer/components/RightDrawerBody'; +import { RightDrawerPage } from '@/ui/layout/right-drawer/components/RightDrawerPage'; +import { RightDrawerTopBar } from '@/ui/layout/right-drawer/components/RightDrawerTopBar'; + +import { Timeline } from '../timeline/Timeline'; + +export function RightDrawerTimeline() { + const [commentableEntityArray] = useRecoilState(commentableEntityArrayState); + + useHotkeysScopeOnMountOnly({ + scope: InternalHotkeysScope.RightDrawer, + customScopes: { goto: false, 'command-menu': true }, + }); + + return ( + + + + {commentableEntityArray.map((commentableEntity) => ( + + ))} + + + ); +} diff --git a/front/src/modules/comments/components/right-drawer/create/RightDrawerCreateCommentThread.tsx b/front/src/modules/comments/components/right-drawer/create/RightDrawerCreateCommentThread.tsx new file mode 100644 index 0000000000..862a47f8ac --- /dev/null +++ b/front/src/modules/comments/components/right-drawer/create/RightDrawerCreateCommentThread.tsx @@ -0,0 +1,38 @@ +import { useRecoilValue } from 'recoil'; + +import { viewableCommentThreadIdState } from '@/comments/states/viewableCommentThreadIdState'; +import { useHotkeysScopeOnMountOnly } from '@/hotkeys/hooks/useHotkeysScopeOnMountOnly'; +import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope'; +import { RightDrawerBody } from '@/ui/layout/right-drawer/components/RightDrawerBody'; +import { RightDrawerPage } from '@/ui/layout/right-drawer/components/RightDrawerPage'; +import { RightDrawerTopBar } from '@/ui/layout/right-drawer/components/RightDrawerTopBar'; + +import { CommentThread } from '../CommentThread'; + +export function RightDrawerCreateCommentThread() { + const commentThreadId = useRecoilValue(viewableCommentThreadIdState); + + useHotkeysScopeOnMountOnly({ + scope: InternalHotkeysScope.RightDrawer, + customScopes: { goto: false, 'command-menu': true }, + }); + + return ( + + { + return; + }} + /> + + {commentThreadId && ( + + )} + + + ); +} diff --git a/front/src/modules/comments/components/RightDrawerCreateCommentThread.tsx b/front/src/modules/comments/components/right-drawer/edit/RightDrawerEditCommentThread.tsx similarity index 61% rename from front/src/modules/comments/components/RightDrawerCreateCommentThread.tsx rename to front/src/modules/comments/components/right-drawer/edit/RightDrawerEditCommentThread.tsx index 78e497dc5f..4fa175b628 100644 --- a/front/src/modules/comments/components/RightDrawerCreateCommentThread.tsx +++ b/front/src/modules/comments/components/right-drawer/edit/RightDrawerEditCommentThread.tsx @@ -1,21 +1,25 @@ +import { useRecoilValue } from 'recoil'; + +import { viewableCommentThreadIdState } from '@/comments/states/viewableCommentThreadIdState'; import { useHotkeysScopeOnMountOnly } from '@/hotkeys/hooks/useHotkeysScopeOnMountOnly'; import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope'; import { RightDrawerBody } from '@/ui/layout/right-drawer/components/RightDrawerBody'; import { RightDrawerPage } from '@/ui/layout/right-drawer/components/RightDrawerPage'; import { RightDrawerTopBar } from '@/ui/layout/right-drawer/components/RightDrawerTopBar'; -import { CommentThreadCreateMode } from './CommentThreadCreateMode'; +import { CommentThread } from '../CommentThread'; -export function RightDrawerCreateCommentThread() { +export function RightDrawerEditCommentThread() { useHotkeysScopeOnMountOnly({ scope: InternalHotkeysScope.RightDrawer, customScopes: { goto: false, 'command-menu': true }, }); + const commentThreadId = useRecoilValue(viewableCommentThreadIdState); return ( - + - + {commentThreadId && } ); diff --git a/front/src/modules/comments/components/CellCommentChip.tsx b/front/src/modules/comments/components/table/CellCommentChip.tsx similarity index 100% rename from front/src/modules/comments/components/CellCommentChip.tsx rename to front/src/modules/comments/components/table/CellCommentChip.tsx diff --git a/front/src/modules/comments/components/CommentChip.tsx b/front/src/modules/comments/components/table/CommentChip.tsx similarity index 92% rename from front/src/modules/comments/components/CommentChip.tsx rename to front/src/modules/comments/components/table/CommentChip.tsx index d40e01c34f..6afe48ce43 100644 --- a/front/src/modules/comments/components/CommentChip.tsx +++ b/front/src/modules/comments/components/table/CommentChip.tsx @@ -41,9 +41,8 @@ const StyledChip = styled.div` const StyledCount = styled.div` align-items: center; display: flex; - font-size: 12px; - - font-weight: 500; + font-size: ${({ theme }) => theme.font.size.sm}; + font-weight: ${({ theme }) => theme.font.weight.medium}; justify-content: center; `; diff --git a/front/src/modules/comments/components/__stories__/CommentChip.stories.tsx b/front/src/modules/comments/components/table/__stories__/CommentChip.stories.tsx similarity index 100% rename from front/src/modules/comments/components/__stories__/CommentChip.stories.tsx rename to front/src/modules/comments/components/table/__stories__/CommentChip.stories.tsx diff --git a/front/src/modules/comments/components/timeline/Timeline.tsx b/front/src/modules/comments/components/timeline/Timeline.tsx new file mode 100644 index 0000000000..5c3c2301f6 --- /dev/null +++ b/front/src/modules/comments/components/timeline/Timeline.tsx @@ -0,0 +1,288 @@ +import React from 'react'; +import { Tooltip } from 'react-tooltip'; +import styled from '@emotion/styled'; + +import { useOpenCommentThreadRightDrawer } from '@/comments/hooks/useOpenCommentThreadRightDrawer'; +import { useOpenCreateCommentThreadDrawer } from '@/comments/hooks/useOpenCreateCommentThreadDrawer'; +import { CommentableEntity } from '@/comments/types/CommentableEntity'; +import { CommentThreadForDrawer } from '@/comments/types/CommentThreadForDrawer'; +import { TableActionBarButtonToggleComments } from '@/ui/components/table/action-bar/TableActionBarButtonOpenComments'; +import { IconCirclePlus, IconNotes } from '@/ui/icons/index'; +import { + beautifyExactDate, + beautifyPastDateRelativeToNow, +} from '@/utils/datetime/date-utils'; +import { + SortOrder, + useGetCommentThreadsByTargetsQuery, +} from '~/generated/graphql'; + +const StyledMainContainer = styled.div` + align-items: flex-start; + align-self: stretch; + display: flex; + flex: 1 0 0; + flex-direction: column; + justify-content: center; +`; + +const StyledTimelineContainer = styled.div` + align-items: center; + align-self: stretch; + display: flex; + flex: 1 0 0; + flex-direction: column; + gap: 4px; + justify-content: flex-start; + overflow-y: auto; + padding: 12px 16px 12px 16px; +`; + +const StyledTimelineEmptyContainer = styled.div` + align-items: center; + align-self: stretch; + display: flex; + flex: 1 0 0; + flex-direction: column; + gap: 8px; + justify-content: center; +`; + +const StyledEmptyTimelineTitle = styled.div` + color: ${({ theme }) => theme.font.color.secondary}; + font-size: ${({ theme }) => theme.font.size.xxl}; + font-weight: ${({ theme }) => theme.font.weight.semiBold}; + line-height: ${({ theme }) => theme.text.lineHeight.md}; +`; + +const StyledEmptyTimelineSubTitle = styled.div` + color: ${({ theme }) => theme.font.color.extraLight}; + font-size: ${({ theme }) => theme.font.size.xxl}; + font-weight: ${({ theme }) => theme.font.weight.semiBold}; + line-height: ${({ theme }) => theme.text.lineHeight.md}; +`; + +const StyledTimelineItemContainer = styled.div` + align-items: center; + align-self: stretch; + display: flex; + gap: 16px; +`; + +const StyledIconContainer = styled.div` + align-items: center; + color: ${({ theme }) => theme.font.color.tertiary}; + display: flex; + height: 20px; + justify-content: center; + width: 20px; +`; + +const StyledItemTitleContainer = styled.div` + align-content: flex-start; + align-items: center; + color: ${({ theme }) => theme.font.color.tertiary}; + display: flex; + flex: 1 0 0; + flex-wrap: wrap; + gap: 4px 8px; + height: 20px; + span { + color: ${({ theme }) => theme.font.color.secondary}; + } +`; + +const StyledItemTitleDate = styled.div` + align-items: center; + color: ${({ theme }) => theme.font.color.tertiary}; + display: flex; + gap: 8px; + justify-content: flex-end; +`; + +const StyledVerticalLineContainer = styled.div` + align-items: center; + align-self: stretch; + display: flex; + gap: 8px; + justify-content: center; + width: 20px; +`; + +const StyledVerticalLine = styled.div` + align-self: stretch; + background: ${({ theme }) => theme.border.color.light}; + flex-shrink: 0; + width: 2px; +`; + +const StyledCardContainer = styled.div` + align-items: center; + cursor: pointer; + display: flex; + flex-direction: column; + gap: 8px; + padding: 4px 0px 20px 0px; +`; + +const StyledCard = styled.div` + align-items: flex-start; + align-self: stretch; + background: ${({ theme }) => theme.background.secondary}; + border: 1px solid ${({ theme }) => theme.border.color.medium}; + border-radius: 4px; + display: flex; + flex-direction: column; + gap: 12px; + max-width: 400px; + padding: 12px; +`; + +const StyledCardTitle = styled.div` + color: ${({ theme }) => theme.font.color.primary}; + font-weight: ${({ theme }) => theme.font.weight.medium}; + line-height: ${({ theme }) => theme.text.lineHeight.lg}; +`; + +const StyledCardContent = styled.div` + -webkit-box-orient: vertical; + + -webkit-line-clamp: 3; + align-self: stretch; + color: ${({ theme }) => theme.font.color.secondary}; + display: -webkit-box; + overflow: hidden; + text-overflow: ellipsis; +`; + +const StyledTooltip = styled(Tooltip)` + background-color: ${({ theme }) => theme.background.primary}; + + box-shadow: 0px 2px 4px 3px + ${({ theme }) => theme.background.transparent.light}; + + box-shadow: 2px 4px 16px 6px + ${({ theme }) => theme.background.transparent.light}; + + color: ${({ theme }) => theme.font.color.primary}; + + opacity: 1; + padding: 8px; +`; + +const StyledTopActionBar = styled.div` + align-items: flex-start; + align-self: stretch; + backdrop-filter: blur(5px); + border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; + border-top-right-radius: 8px; + display: flex; + flex-direction: column; + left: 0px; + padding: 12px 16px 12px 16px; + position: sticky; + top: 0px; +`; + +export function Timeline({ entity }: { entity: CommentableEntity }) { + const { data: queryResult } = useGetCommentThreadsByTargetsQuery({ + variables: { + commentThreadTargetIds: [entity.id], + orderBy: [ + { + createdAt: SortOrder.Desc, + }, + ], + }, + }); + + const openCommentThreadRightDrawer = useOpenCommentThreadRightDrawer(); + + const openCreateCommandThread = useOpenCreateCommentThreadDrawer(); + + const commentThreads: CommentThreadForDrawer[] = + queryResult?.findManyCommentThreads ?? []; + + if (!commentThreads.length) { + return ( + + No activity yet + Create one: + openCreateCommandThread(entity)} + /> + + ); + } + + return ( + + + + + + + + openCreateCommandThread(entity)} + /> + + + + {commentThreads.map((commentThread) => { + const beautifiedCreatedAt = beautifyPastDateRelativeToNow( + commentThread.createdAt, + ); + const exactCreatedAt = beautifyExactDate(commentThread.createdAt); + const body = JSON.parse(commentThread.body ?? '{}')[0]?.content[0] + ?.text; + + return ( + + + + + + + + {commentThread.author.firstName}{' '} + {commentThread.author.lastName} + + created a note + + + {beautifiedCreatedAt} ago + + + + + + + + + + openCommentThreadRightDrawer(commentThread.id) + } + > + + {commentThread.title ? commentThread.title : '(No title)'} + + + {body ? body : '(No content)'} + + + + + + ); + })} + + + ); +} diff --git a/front/src/modules/comments/hooks/useHandleCheckableCommentThreadTargetChange.ts b/front/src/modules/comments/hooks/useHandleCheckableCommentThreadTargetChange.ts index 1c0bcf52eb..b544878296 100644 --- a/front/src/modules/comments/hooks/useHandleCheckableCommentThreadTargetChange.ts +++ b/front/src/modules/comments/hooks/useHandleCheckableCommentThreadTargetChange.ts @@ -4,18 +4,23 @@ import { v4 } from 'uuid'; import { GET_COMPANIES } from '@/companies/services'; import { GET_PEOPLE } from '@/people/services'; import { + CommentThread, + CommentThreadTarget, useAddCommentThreadTargetOnCommentThreadMutation, useRemoveCommentThreadTargetOnCommentThreadMutation, } from '~/generated/graphql'; import { GET_COMMENT_THREADS_BY_TARGETS } from '../services'; import { CommentableEntityForSelect } from '../types/CommentableEntityForSelect'; -import { CommentThreadForDrawer } from '../types/CommentThreadForDrawer'; export function useHandleCheckableCommentThreadTargetChange({ commentThread, }: { - commentThread: CommentThreadForDrawer; + commentThread?: Pick & { + commentThreadTargets: Array< + Pick + >; + }; }) { const [addCommentThreadTargetOnCommentThread] = useAddCommentThreadTargetOnCommentThreadMutation({ @@ -39,6 +44,9 @@ export function useHandleCheckableCommentThreadTargetChange({ newCheckedValue: boolean, entity: CommentableEntityForSelect, ) { + if (!commentThread) { + return; + } if (newCheckedValue) { addCommentThreadTargetOnCommentThread({ variables: { diff --git a/front/src/modules/comments/hooks/useOpenCommentThreadRightDrawer.ts b/front/src/modules/comments/hooks/useOpenCommentThreadRightDrawer.ts new file mode 100644 index 0000000000..f7254b40cb --- /dev/null +++ b/front/src/modules/comments/hooks/useOpenCommentThreadRightDrawer.ts @@ -0,0 +1,18 @@ +import { useRecoilState } from 'recoil'; + +import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages'; + +import { useOpenRightDrawer } from '../../ui/layout/right-drawer/hooks/useOpenRightDrawer'; +import { viewableCommentThreadIdState } from '../states/viewableCommentThreadIdState'; + +export function useOpenCommentThreadRightDrawer() { + const openRightDrawer = useOpenRightDrawer(); + const [, setViewableCommentThreadId] = useRecoilState( + viewableCommentThreadIdState, + ); + + return function openCommentThreadRightDrawer(commentThreadId: string) { + setViewableCommentThreadId(commentThreadId); + openRightDrawer(RightDrawerPages.EditCommentThread); + }; +} diff --git a/front/src/modules/comments/hooks/useOpenCreateCommentDrawerForSelectedRowIds.ts b/front/src/modules/comments/hooks/useOpenCreateCommentDrawerForSelectedRowIds.ts index 63a327a984..5bf8dc5eff 100644 --- a/front/src/modules/comments/hooks/useOpenCreateCommentDrawerForSelectedRowIds.ts +++ b/front/src/modules/comments/hooks/useOpenCreateCommentDrawerForSelectedRowIds.ts @@ -1,38 +1,73 @@ +import { getOperationName } from '@apollo/client/utilities/graphql/getFromAST'; import { useRecoilState, useRecoilValue } from 'recoil'; +import { v4 } from 'uuid'; +import { currentUserState } from '@/auth/states/currentUserState'; +import { GET_COMPANIES } from '@/companies/services'; +import { GET_PEOPLE } from '@/people/services'; +import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages'; import { selectedRowIdsState } from '@/ui/tables/states/selectedRowIdsState'; -import { CommentableType } from '~/generated/graphql'; +import { + CommentableType, + useCreateCommentThreadMutation, +} from '~/generated/graphql'; import { useOpenRightDrawer } from '../../ui/layout/right-drawer/hooks/useOpenRightDrawer'; +import { + GET_COMMENT_THREAD, + GET_COMMENT_THREADS_BY_TARGETS, +} from '../services'; import { commentableEntityArrayState } from '../states/commentableEntityArrayState'; -import { createdCommentThreadIdState } from '../states/createdCommentThreadIdState'; +import { viewableCommentThreadIdState } from '../states/viewableCommentThreadIdState'; import { CommentableEntity } from '../types/CommentableEntity'; export function useOpenCreateCommentThreadDrawerForSelectedRowIds() { const openRightDrawer = useOpenRightDrawer(); + const [createCommentThreadMutation] = useCreateCommentThreadMutation(); + const currentUser = useRecoilValue(currentUserState); + const [, setViewableCommentThreadId] = useRecoilState( + viewableCommentThreadIdState, + ); const [, setCommentableEntityArray] = useRecoilState( commentableEntityArrayState, ); - const [, setCreatedCommentThreadId] = useRecoilState( - createdCommentThreadIdState, - ); - - const selectedPeopleIds = useRecoilValue(selectedRowIdsState); + const selectedEntityIds = useRecoilValue(selectedRowIdsState); return function openCreateCommentDrawerForSelectedRowIds( entityType: CommentableType, ) { - const commentableEntityArray: CommentableEntity[] = selectedPeopleIds.map( + const commentableEntityArray: CommentableEntity[] = selectedEntityIds.map( (id) => ({ type: entityType, id, }), ); - setCreatedCommentThreadId(null); - setCommentableEntityArray(commentableEntityArray); - openRightDrawer('create-comment-thread'); + createCommentThreadMutation({ + variables: { + authorId: currentUser?.id ?? '', + commentThreadId: v4(), + createdAt: new Date().toISOString(), + commentThreadTargetArray: commentableEntityArray.map((entity) => ({ + commentableId: entity.id, + commentableType: entity.type, + id: v4(), + createdAt: new Date().toISOString(), + })), + }, + refetchQueries: [ + getOperationName(GET_COMPANIES) ?? '', + getOperationName(GET_PEOPLE) ?? '', + getOperationName(GET_COMMENT_THREAD) ?? '', + getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '', + ], + onCompleted(data) { + setViewableCommentThreadId(data.createOneCommentThread.id); + setCommentableEntityArray(commentableEntityArray); + openRightDrawer(RightDrawerPages.CreateCommentThread); + }, + }); }; } diff --git a/front/src/modules/comments/hooks/useOpenCreateCommentThreadDrawer.ts b/front/src/modules/comments/hooks/useOpenCreateCommentThreadDrawer.ts new file mode 100644 index 0000000000..1447f34342 --- /dev/null +++ b/front/src/modules/comments/hooks/useOpenCreateCommentThreadDrawer.ts @@ -0,0 +1,60 @@ +import { getOperationName } from '@apollo/client/utilities'; +import { useRecoilState, useRecoilValue } from 'recoil'; +import { v4 } from 'uuid'; + +import { currentUserState } from '@/auth/states/currentUserState'; +import { GET_COMPANIES } from '@/companies/services'; +import { GET_PEOPLE } from '@/people/services'; +import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages'; +import { useCreateCommentThreadMutation } from '~/generated/graphql'; + +import { useOpenRightDrawer } from '../../ui/layout/right-drawer/hooks/useOpenRightDrawer'; +import { + GET_COMMENT_THREAD, + GET_COMMENT_THREADS_BY_TARGETS, +} from '../services'; +import { commentableEntityArrayState } from '../states/commentableEntityArrayState'; +import { viewableCommentThreadIdState } from '../states/viewableCommentThreadIdState'; +import { CommentableEntity } from '../types/CommentableEntity'; + +export function useOpenCreateCommentThreadDrawer() { + const openRightDrawer = useOpenRightDrawer(); + const [createCommentThreadMutation] = useCreateCommentThreadMutation(); + const currentUser = useRecoilValue(currentUserState); + + const [, setCommentableEntityArray] = useRecoilState( + commentableEntityArrayState, + ); + const [, setViewableCommentThreadId] = useRecoilState( + viewableCommentThreadIdState, + ); + + return function openCreateCommentThreadDrawer(entity: CommentableEntity) { + createCommentThreadMutation({ + variables: { + authorId: currentUser?.id ?? '', + commentThreadId: v4(), + createdAt: new Date().toISOString(), + commentThreadTargetArray: [ + { + commentableId: entity.id, + commentableType: entity.type, + id: v4(), + createdAt: new Date().toISOString(), + }, + ], + }, + refetchQueries: [ + getOperationName(GET_COMPANIES) ?? '', + getOperationName(GET_PEOPLE) ?? '', + getOperationName(GET_COMMENT_THREAD) ?? '', + getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '', + ], + onCompleted(data) { + setViewableCommentThreadId(data.createOneCommentThread.id); + setCommentableEntityArray([entity]); + openRightDrawer(RightDrawerPages.CreateCommentThread); + }, + }); + }; +} diff --git a/front/src/modules/comments/hooks/useOpenCommentRightDrawer.ts b/front/src/modules/comments/hooks/useOpenTimelineRightDrawer.ts similarity index 70% rename from front/src/modules/comments/hooks/useOpenCommentRightDrawer.ts rename to front/src/modules/comments/hooks/useOpenTimelineRightDrawer.ts index 6f237e681d..b9edffcf34 100644 --- a/front/src/modules/comments/hooks/useOpenCommentRightDrawer.ts +++ b/front/src/modules/comments/hooks/useOpenTimelineRightDrawer.ts @@ -1,19 +1,21 @@ import { useRecoilState } from 'recoil'; +import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages'; + import { useOpenRightDrawer } from '../../ui/layout/right-drawer/hooks/useOpenRightDrawer'; import { commentableEntityArrayState } from '../states/commentableEntityArrayState'; import { CommentableEntity } from '../types/CommentableEntity'; -export function useOpenCommentRightDrawer() { +export function useOpenTimelineRightDrawer() { const openRightDrawer = useOpenRightDrawer(); const [, setCommentableEntityArray] = useRecoilState( commentableEntityArrayState, ); - return function openCommentRightDrawer( + return function openTimelineRightDrawer( commentableEntityArray: CommentableEntity[], ) { setCommentableEntityArray(commentableEntityArray); - openRightDrawer('comments'); + openRightDrawer(RightDrawerPages.Timeline); }; } diff --git a/front/src/modules/comments/services/create.ts b/front/src/modules/comments/services/create.ts index 83b721ab81..567b1c49a3 100644 --- a/front/src/modules/comments/services/create.ts +++ b/front/src/modules/comments/services/create.ts @@ -33,12 +33,12 @@ export const CREATE_COMMENT = gql` `; export const CREATE_COMMENT_THREAD_WITH_COMMENT = gql` - mutation CreateCommentThreadWithComment( + mutation CreateCommentThread( $commentThreadId: String! - $commentText: String! + $body: String + $title: String $authorId: String! $createdAt: DateTime! - $commentId: String! $commentThreadTargetArray: [CommentThreadTargetCreateManyCommentThreadInput!]! ) { createOneCommentThread( @@ -46,16 +46,9 @@ export const CREATE_COMMENT_THREAD_WITH_COMMENT = gql` id: $commentThreadId createdAt: $createdAt updatedAt: $createdAt - comments: { - createMany: { - data: { - authorId: $authorId - id: $commentId - createdAt: $createdAt - body: $commentText - } - } - } + author: { connect: { id: $authorId } } + body: $body + title: $title commentThreadTargets: { createMany: { data: $commentThreadTargetArray, skipDuplicates: true } } @@ -64,6 +57,7 @@ export const CREATE_COMMENT_THREAD_WITH_COMMENT = gql` id createdAt updatedAt + authorId commentThreadTargets { id createdAt diff --git a/front/src/modules/comments/services/select.ts b/front/src/modules/comments/services/select.ts index 694d1b1559..82b4851f17 100644 --- a/front/src/modules/comments/services/select.ts +++ b/front/src/modules/comments/services/select.ts @@ -14,6 +14,14 @@ export const GET_COMMENT_THREADS_BY_TARGETS = gql` } ) { id + createdAt + title + body + author { + id + firstName + lastName + } comments { id body @@ -40,6 +48,14 @@ export const GET_COMMENT_THREAD = gql` query GetCommentThread($commentThreadId: String!) { findManyCommentThreads(where: { id: { equals: $commentThreadId } }) { id + createdAt + body + title + author { + id + firstName + lastName + } comments { id body @@ -54,6 +70,7 @@ export const GET_COMMENT_THREAD = gql` } } commentThreadTargets { + id commentableId commentableType } diff --git a/front/src/modules/comments/services/update.ts b/front/src/modules/comments/services/update.ts index 6cee16a11c..6e45b20290 100644 --- a/front/src/modules/comments/services/update.ts +++ b/front/src/modules/comments/services/update.ts @@ -68,3 +68,33 @@ export const DELETE_COMMENT_THREAD = gql` } } `; + +export const UPDATE_COMMENT_THREAD_TITLE = gql` + mutation UpdateCommentThreadTitle( + $commentThreadId: String! + $commentThreadTitle: String + ) { + updateOneCommentThread( + where: { id: $commentThreadId } + data: { title: { set: $commentThreadTitle } } + ) { + id + title + } + } +`; + +export const UPDATE_COMMENT_THREAD_BODY = gql` + mutation UpdateCommentThreadBody( + $commentThreadId: String! + $commentThreadBody: String + ) { + updateOneCommentThread( + where: { id: $commentThreadId } + data: { body: { set: $commentThreadBody } } + ) { + id + body + } + } +`; diff --git a/front/src/modules/comments/states/createdCommentThreadIdState.ts b/front/src/modules/comments/states/createdCommentThreadIdState.ts deleted file mode 100644 index 47809154ea..0000000000 --- a/front/src/modules/comments/states/createdCommentThreadIdState.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { atom } from 'recoil'; - -export const createdCommentThreadIdState = atom({ - key: 'comments/created-comment-thread-id', - default: null, -}); diff --git a/front/src/modules/comments/states/viewableCommentThreadIdState.ts b/front/src/modules/comments/states/viewableCommentThreadIdState.ts new file mode 100644 index 0000000000..0caff99735 --- /dev/null +++ b/front/src/modules/comments/states/viewableCommentThreadIdState.ts @@ -0,0 +1,6 @@ +import { atom } from 'recoil'; + +export const viewableCommentThreadIdState = atom({ + key: 'comments/viewable-comment-thread-id', + default: null, +}); diff --git a/front/src/modules/companies/components/CompanyChip.tsx b/front/src/modules/companies/components/CompanyChip.tsx index a0f6a389cc..91e3cf94f8 100644 --- a/front/src/modules/companies/components/CompanyChip.tsx +++ b/front/src/modules/companies/components/CompanyChip.tsx @@ -1,23 +1,27 @@ +import { Link } from 'react-router-dom'; +import { Theme } from '@emotion/react'; import styled from '@emotion/styled'; import { Avatar } from '@/users/components/Avatar'; export type CompanyChipPropsType = { + id?: string; name: string; picture?: string; }; -const StyledContainer = styled.span` +const baseStyle = ({ theme }: { theme: Theme }) => ` align-items: center; - background-color: ${({ theme }) => theme.background.tertiary}; - border-radius: ${({ theme }) => theme.spacing(1)}; - color: ${({ theme }) => theme.font.color.primary}; + background-color: ${theme.background.tertiary}; + border-radius: ${theme.spacing(1)}; + color: ${theme.font.color.primary}; display: inline-flex; - gap: ${({ theme }) => theme.spacing(1)}; - height: calc(20px - 2 * ${({ theme }) => theme.spacing(1)}); + gap: ${theme.spacing(1)}; + height: calc(20px - 2 * ${theme.spacing(1)}); overflow: hidden; + padding: ${theme.spacing(1)}; - padding: ${({ theme }) => theme.spacing(1)}; + text-decoration: none; user-select: none; @@ -38,9 +42,19 @@ const StyledName = styled.span` white-space: nowrap; `; -function CompanyChip({ name, picture }: CompanyChipPropsType) { +const StyledContainerLink = styled(Link)` + ${baseStyle} +`; + +const StyledContainerNoLink = styled.div` + ${baseStyle} +`; + +function CompanyChip({ id, name, picture }: CompanyChipPropsType) { + const ContainerComponent = id ? StyledContainerLink : StyledContainerNoLink; + return ( - + {picture && ( )} {name} - + ); } diff --git a/front/src/modules/companies/components/CompanyEditableNameCell.tsx b/front/src/modules/companies/components/CompanyEditableNameCell.tsx index c2b9fd3aca..12d888ba76 100644 --- a/front/src/modules/companies/components/CompanyEditableNameCell.tsx +++ b/front/src/modules/companies/components/CompanyEditableNameCell.tsx @@ -1,5 +1,5 @@ -import { CellCommentChip } from '@/comments/components/CellCommentChip'; -import { useOpenCommentRightDrawer } from '@/comments/hooks/useOpenCommentRightDrawer'; +import { CellCommentChip } from '@/comments/components/table/CellCommentChip'; +import { useOpenTimelineRightDrawer } from '@/comments/hooks/useOpenTimelineRightDrawer'; import { EditableCellChip } from '@/ui/components/editable-cell/types/EditableChip'; import { getLogoUrlFromDomainName } from '@/utils/utils'; import { @@ -13,12 +13,12 @@ import CompanyChip from './CompanyChip'; type OwnProps = { company: Pick< GetCompaniesQuery['companies'][0], - 'id' | 'name' | 'domainName' | '_commentCount' | 'accountOwner' + 'id' | 'name' | 'domainName' | '_commentThreadCount' | 'accountOwner' >; }; export function CompanyEditableNameChipCell({ company }: OwnProps) { - const openCommentRightDrawer = useOpenCommentRightDrawer(); + const openCommentRightDrawer = useOpenTimelineRightDrawer(); const [updateCompany] = useUpdateCompanyMutation(); function handleCommentClick(event: React.MouseEvent) { @@ -38,6 +38,7 @@ export function CompanyEditableNameChipCell({ company }: OwnProps) { value={company.name || ''} placeholder="Name" picture={getLogoUrlFromDomainName(company.domainName)} + id={company.id} changeHandler={(value: string) => { updateCompany({ variables: { @@ -50,7 +51,7 @@ export function CompanyEditableNameChipCell({ company }: OwnProps) { ChipComponent={CompanyChip} rightEndContents={[ , ]} diff --git a/front/src/modules/companies/components/__stories__/CompanyChip.stories.tsx b/front/src/modules/companies/components/__stories__/CompanyChip.stories.tsx index 0d5439a0a8..54c37f3dde 100644 --- a/front/src/modules/companies/components/__stories__/CompanyChip.stories.tsx +++ b/front/src/modules/companies/components/__stories__/CompanyChip.stories.tsx @@ -33,8 +33,8 @@ export const SmallName: Story = { render: getRenderWrapperForComponent( , ), diff --git a/front/src/modules/companies/services/index.ts b/front/src/modules/companies/services/index.ts index 18c6c2f7dd..41161a6b18 100644 --- a/front/src/modules/companies/services/index.ts +++ b/front/src/modules/companies/services/index.ts @@ -1,2 +1,3 @@ export * from './select'; +export * from './show'; export * from './update'; diff --git a/front/src/modules/companies/services/select.ts b/front/src/modules/companies/services/select.ts index 96373225b8..1077d33341 100644 --- a/front/src/modules/companies/services/select.ts +++ b/front/src/modules/companies/services/select.ts @@ -22,7 +22,7 @@ export const GET_COMPANIES = gql` createdAt address employees - _commentCount + _commentThreadCount accountOwner { id email diff --git a/front/src/modules/companies/services/show.ts b/front/src/modules/companies/services/show.ts new file mode 100644 index 0000000000..da4db603f2 --- /dev/null +++ b/front/src/modules/companies/services/show.ts @@ -0,0 +1,26 @@ +import { gql } from '@apollo/client'; + +import { useGetCompanyQuery } from '~/generated/graphql'; + +export const GET_COMPANY = gql` + query GetCompany($id: String!) { + findUniqueCompany(id: $id) { + id + domainName + name + createdAt + address + employees + _commentThreadCount + accountOwner { + id + email + displayName + } + } + } +`; + +export function useCompanyQuery(id: string) { + return useGetCompanyQuery({ variables: { id } }); +} diff --git a/front/src/modules/hotkeys/types/internal/InternalHotkeysScope.ts b/front/src/modules/hotkeys/types/internal/InternalHotkeysScope.ts index 23bf4efbae..1c46dee4c5 100644 --- a/front/src/modules/hotkeys/types/internal/InternalHotkeysScope.ts +++ b/front/src/modules/hotkeys/types/internal/InternalHotkeysScope.ts @@ -16,4 +16,5 @@ export enum InternalHotkeysScope { PasswordLogin = 'password-login', AuthIndex = 'auth-index', CreateProfile = 'create-profile', + ShowPage = 'show-page', } diff --git a/front/src/modules/people/components/EditablePeopleFullName.tsx b/front/src/modules/people/components/EditablePeopleFullName.tsx index 17f219422b..5b83de8e16 100644 --- a/front/src/modules/people/components/EditablePeopleFullName.tsx +++ b/front/src/modules/people/components/EditablePeopleFullName.tsx @@ -1,15 +1,15 @@ import { useState } from 'react'; import styled from '@emotion/styled'; -import { CellCommentChip } from '@/comments/components/CellCommentChip'; -import { useOpenCommentRightDrawer } from '@/comments/hooks/useOpenCommentRightDrawer'; +import { CellCommentChip } from '@/comments/components/table/CellCommentChip'; +import { useOpenTimelineRightDrawer } from '@/comments/hooks/useOpenTimelineRightDrawer'; import { EditableCellDoubleText } from '@/ui/components/editable-cell/types/EditableCellDoubleText'; import { CommentableType, Person } from '~/generated/graphql'; import { PersonChip } from './PersonChip'; type OwnProps = { - person: Pick; + person: Pick; onChange: (firstName: string, lastName: string) => void; }; @@ -27,7 +27,7 @@ const RightContainer = styled.div` export function EditablePeopleFullName({ person, onChange }: OwnProps) { const [firstNameValue, setFirstNameValue] = useState(person.firstName ?? ''); const [lastNameValue, setLastNameValue] = useState(person.lastName ?? ''); - const openCommentRightDrawer = useOpenCommentRightDrawer(); + const openCommentRightDrawer = useOpenTimelineRightDrawer(); function handleDoubleTextChange( firstValue: string, @@ -60,10 +60,13 @@ export function EditablePeopleFullName({ person, onChange }: OwnProps) { onChange={handleDoubleTextChange} nonEditModeContent={ - + diff --git a/front/src/modules/people/components/PeopleCompanyCell.tsx b/front/src/modules/people/components/PeopleCompanyCell.tsx index e6ccd7c37f..4df2f16029 100644 --- a/front/src/modules/people/components/PeopleCompanyCell.tsx +++ b/front/src/modules/people/components/PeopleCompanyCell.tsx @@ -30,6 +30,7 @@ export function PeopleCompanyCell({ people }: OwnProps) { } nonEditModeContent={ diff --git a/front/src/modules/people/components/PersonChip.tsx b/front/src/modules/people/components/PersonChip.tsx index 6f14de7664..e7b656cebb 100644 --- a/front/src/modules/people/components/PersonChip.tsx +++ b/front/src/modules/people/components/PersonChip.tsx @@ -1,54 +1,63 @@ import * as React from 'react'; +import { Link } from 'react-router-dom'; +import { Theme } from '@emotion/react'; import styled from '@emotion/styled'; import PersonPlaceholder from './person-placeholder.png'; export type PersonChipPropsType = { + id?: string; name: string; picture?: string; }; -const StyledContainer = styled.span` +const baseStyle = ({ theme }: { theme: Theme }) => ` align-items: center; - background-color: ${({ theme }) => theme.background.tertiary}; - border-radius: ${({ theme }) => theme.spacing(1)}; - color: ${({ theme }) => theme.font.color.primary}; + background-color: ${theme.background.tertiary}; + border-radius: ${theme.spacing(1)}; + color: ${theme.font.color.primary}; display: inline-flex; - gap: ${({ theme }) => theme.spacing(1)}; + gap: ${theme.spacing(1)}; height: 12px; - overflow: hidden; - padding: ${({ theme }) => theme.spacing(1)}; - + padding: ${theme.spacing(1)}; + text-decoration: none; :hover { filter: brightness(95%); } - img { border-radius: 100%; height: 14px; object-fit: cover; width: 14px; } - white-space: nowrap; `; +const StyledContainerLink = styled(Link)` + ${baseStyle} +`; + +const StyledContainerNoLink = styled.div` + ${baseStyle} +`; + const StyledName = styled.span` overflow: hidden; text-overflow: ellipsis; white-space: nowrap; `; -export function PersonChip({ name, picture }: PersonChipPropsType) { +export function PersonChip({ id, name, picture }: PersonChipPropsType) { + const ContainerComponent = id ? StyledContainerLink : StyledContainerNoLink; return ( - + person {name} - + ); } diff --git a/front/src/modules/people/services/index.ts b/front/src/modules/people/services/index.ts index 18c6c2f7dd..41161a6b18 100644 --- a/front/src/modules/people/services/index.ts +++ b/front/src/modules/people/services/index.ts @@ -1,2 +1,3 @@ export * from './select'; +export * from './show'; export * from './update'; diff --git a/front/src/modules/people/services/select.ts b/front/src/modules/people/services/select.ts index 2b1d1d39c9..1a732d1fb9 100644 --- a/front/src/modules/people/services/select.ts +++ b/front/src/modules/people/services/select.ts @@ -24,7 +24,7 @@ export const GET_PEOPLE = gql` firstName lastName createdAt - _commentCount + _commentThreadCount company { id name diff --git a/front/src/modules/people/services/show.ts b/front/src/modules/people/services/show.ts new file mode 100644 index 0000000000..96a4e4a219 --- /dev/null +++ b/front/src/modules/people/services/show.ts @@ -0,0 +1,19 @@ +import { gql } from '@apollo/client'; + +import { useGetPersonQuery } from '~/generated/graphql'; + +export const GET_PERSON = gql` + query GetPerson($id: String!) { + findUniquePerson(id: $id) { + id + firstName + lastName + displayName + createdAt + } + } +`; + +export function usePersonQuery(id: string) { + return useGetPersonQuery({ variables: { id } }); +} diff --git a/front/src/modules/ui/components/buttons/DropdownButton.tsx b/front/src/modules/ui/components/buttons/DropdownButton.tsx new file mode 100644 index 0000000000..dad76d90f6 --- /dev/null +++ b/front/src/modules/ui/components/buttons/DropdownButton.tsx @@ -0,0 +1,94 @@ +import React, { useState } from 'react'; +import styled from '@emotion/styled'; + +import { IconChevronDown } from '@/ui/icons/index'; + +type ButtonProps = React.ComponentProps<'button'>; + +export type DropdownOptionType = { + label: string; + icon: React.ReactNode; +}; + +type Props = { + options: DropdownOptionType[]; + onSelection: (value: DropdownOptionType) => void; +} & ButtonProps; + +const StyledButton = styled.button` + align-items: center; + background: ${({ theme }) => theme.background.tertiary}; + border: 1px solid ${({ theme }) => theme.border.color.medium}; + border-radius: 4px; + color: ${({ theme }) => theme.font.color.secondary}; + display: flex; + font-size: ${({ theme }) => theme.font.size.md}; + font-weight: ${({ theme }) => theme.font.weight.medium}; + gap: 8px; + height: 24px; + line-height: ${({ theme }) => theme.text.lineHeight.lg}; + padding: 3px 8px; + + svg { + align-items: center; + display: flex; + height: 14px; + justify-content: center; + width: 14px; + } +`; + +const DropdownContainer = styled.div` + position: relative; +`; + +const DropdownMenu = styled.div` + display: flex; + flex-direction: column; + margin-top: -2px; + position: absolute; +`; + +export function DropdownButton({ + options, + onSelection, + ...buttonProps +}: Props) { + const [isOpen, setIsOpen] = useState(false); + const [selectedOption, setSelectedOption] = useState(options[0]); + + if (!options.length) { + throw new Error('You must provide at least one option.'); + } + + const handleSelect = + (option: DropdownOptionType) => + (event: React.MouseEvent) => { + event.preventDefault(); + onSelection(option); + setSelectedOption(option); + setIsOpen(false); + }; + + return ( + + setIsOpen(!isOpen)} {...buttonProps}> + {selectedOption.icon} + {selectedOption.label} + {options.length > 1 && } + + {isOpen && ( + + {options + .filter((option) => option.label !== selectedOption.label) + .map((option, index) => ( + + {option.icon} + {option.label} + + ))} + + )} + + ); +} diff --git a/front/src/modules/ui/components/editable-cell/types/EditableChip.tsx b/front/src/modules/ui/components/editable-cell/types/EditableChip.tsx index 089b66650c..3ff79ae68d 100644 --- a/front/src/modules/ui/components/editable-cell/types/EditableChip.tsx +++ b/front/src/modules/ui/components/editable-cell/types/EditableChip.tsx @@ -6,17 +6,19 @@ import { textInputStyle } from '@/ui/themes/effects'; import { EditableCell } from '../EditableCell'; export type EditableChipProps = { + id: string; placeholder?: string; value: string; picture: string; changeHandler: (updated: string) => void; editModeHorizontalAlign?: 'left' | 'right'; ChipComponent: ComponentType<{ + id: string; name: string; picture: string; isOverlapped?: boolean; }>; - commentCount?: number; + commentThreadCount?: number; onCommentClick?: (event: React.MouseEvent) => void; rightEndContents?: ReactNode[]; }; @@ -41,6 +43,7 @@ const RightContainer = styled.div` // TODO: move right end content in EditableCell export function EditableCellChip({ + id, value, placeholder, changeHandler, @@ -75,7 +78,7 @@ export function EditableCellChip({ } nonEditModeContent={ - + {rightEndContents && rightEndContents.length > 0 && diff --git a/front/src/modules/ui/components/editor/BlockEditor.tsx b/front/src/modules/ui/components/editor/BlockEditor.tsx new file mode 100644 index 0000000000..48ef628b4e --- /dev/null +++ b/front/src/modules/ui/components/editor/BlockEditor.tsx @@ -0,0 +1,28 @@ +import { BlockNoteEditor } from '@blocknote/core'; +import { BlockNoteView } from '@blocknote/react'; +import styled from '@emotion/styled'; + +interface BlockEditorProps { + editor: BlockNoteEditor | null; +} + +const StyledEditor = styled.div` + min-height: 200px; + width: 100%; + & .editor-create-mode, + .editor-edit-mode { + background: ${({ theme }) => theme.background.primary}; + } + & .editor-create-mode [class^='_inlineContent']:before { + color: ${({ theme }) => theme.font.color.tertiary}; + font-style: normal !important; + } +`; + +export function BlockEditor({ editor }: BlockEditorProps) { + return ( + + + + ); +} diff --git a/front/src/modules/ui/components/form/DatePicker.tsx b/front/src/modules/ui/components/form/DatePicker.tsx index a9cbf801b4..575827aa77 100644 --- a/front/src/modules/ui/components/form/DatePicker.tsx +++ b/front/src/modules/ui/components/form/DatePicker.tsx @@ -22,7 +22,7 @@ const StyledContainer = styled.div` font-size: ${({ theme }) => theme.font.size.md}; border: none; display: block; - font-weight: 400; + font-weight: ${({ theme }) => theme.font.weight.regular}; } & .react-datepicker-popper { diff --git a/front/src/modules/ui/components/inputs/AutosizeTextInput.tsx b/front/src/modules/ui/components/inputs/AutosizeTextInput.tsx index 546f260bce..7ce3758782 100644 --- a/front/src/modules/ui/components/inputs/AutosizeTextInput.tsx +++ b/front/src/modules/ui/components/inputs/AutosizeTextInput.tsx @@ -27,8 +27,8 @@ const StyledTextArea = styled(TextareaAutosize)` border-radius: 5px; color: ${({ theme }) => theme.font.color.primary}; font-family: inherit; - font-size: 13px; - font-weight: 400; + font-size: ${({ theme }) => theme.font.size.md}; + font-weight: ${({ theme }) => theme.font.weight.regular}; line-height: 16px; overflow: auto; padding: 8px; @@ -42,7 +42,7 @@ const StyledTextArea = styled(TextareaAutosize)` &::placeholder { color: ${({ theme }) => theme.font.color.light}; - font-weight: 400; + font-weight: ${({ theme }) => theme.font.weight.regular}; } `; @@ -121,7 +121,7 @@ export function AutosizeTextInput({ <> theme.background.secondary}; + border: 1px solid ${({ theme }) => theme.border.color.medium}; + border-radius: 4px; + display: flex; + flex-direction: column; + gap: ${({ theme }) => theme.spacing(2)}; + padding: ${({ theme }) => theme.spacing(2)} ${({ theme }) => theme.spacing(3)}; +`; + +interface PropertyBoxProps { + children: JSX.Element; + extraPadding?: boolean; +} + +export function PropertyBox({ children }: PropertyBoxProps) { + return {children}; +} diff --git a/front/src/modules/ui/components/property-box/PropertyBoxItem.tsx b/front/src/modules/ui/components/property-box/PropertyBoxItem.tsx new file mode 100644 index 0000000000..eabee861be --- /dev/null +++ b/front/src/modules/ui/components/property-box/PropertyBoxItem.tsx @@ -0,0 +1,56 @@ +import { ReactNode } from 'react'; +import styled from '@emotion/styled'; + +const StyledPropertyBoxItem = styled.div` + display: flex; + gap: ${({ theme }) => theme.spacing(2)}; +`; + +const StyledIconContainer = styled.div` + align-items: center; + display: flex; + + svg { + align-items: center; + display: flex; + height: 16px; + justify-content: center; + width: 16px; + } +`; + +const StyledValueContainer = styled.div` + align-content: flex-start; + align-items: center; + color: ${({ theme }) => theme.font.color.primary}; + display: flex; + flex: 1 0 0; + flex-wrap: wrap; +`; + +const StyledLabelAndIconContainer = styled.div` + align-items: center; + color: ${({ theme }) => theme.font.color.tertiary}; + display: flex; + gap: ${({ theme }) => theme.spacing(1)}; +`; + +export function PropertyBoxItem({ + icon, + label, + value, +}: { + icon: ReactNode; + label?: string; + value: ReactNode; +}) { + return ( + + + {icon} + {label} + + {value} + + ); +} diff --git a/front/src/modules/ui/components/section-titles/MainSectionTitle.tsx b/front/src/modules/ui/components/section-titles/MainSectionTitle.tsx index c0dc5e24cf..a8f5be8411 100644 --- a/front/src/modules/ui/components/section-titles/MainSectionTitle.tsx +++ b/front/src/modules/ui/components/section-titles/MainSectionTitle.tsx @@ -9,7 +9,7 @@ const StyledMainSectionTitle = styled.h2` color: ${({ theme }) => theme.font.color.primary}; font-size: ${({ theme }) => theme.font.size.xl}; font-weight: ${({ theme }) => theme.font.weight.semiBold}; - line-height: 1.5; + line-height: ${({ theme }) => theme.text.lineHeight.lg}; margin: 0; `; diff --git a/front/src/modules/ui/components/table/ColumnHead.tsx b/front/src/modules/ui/components/table/ColumnHead.tsx index 667e6f6059..4a3b3d5855 100644 --- a/front/src/modules/ui/components/table/ColumnHead.tsx +++ b/front/src/modules/ui/components/table/ColumnHead.tsx @@ -10,7 +10,7 @@ const StyledTitle = styled.div` align-items: center; display: flex; flex-direction: row; - font-weight: 500; + font-weight: ${({ theme }) => theme.font.weight.medium}; height: ${({ theme }) => theme.spacing(8)}; padding-left: ${({ theme }) => theme.spacing(2)}; `; diff --git a/front/src/modules/ui/components/table/action-bar/EntityTableActionBarButton.tsx b/front/src/modules/ui/components/table/action-bar/EntityTableActionBarButton.tsx index 98b8e9cbc9..24c15c8380 100644 --- a/front/src/modules/ui/components/table/action-bar/EntityTableActionBarButton.tsx +++ b/front/src/modules/ui/components/table/action-bar/EntityTableActionBarButton.tsx @@ -32,7 +32,7 @@ const StyledButton = styled.div` `; const StyledButtonLabel = styled.div` - font-weight: 500; + font-weight: ${({ theme }) => theme.font.weight.medium}; margin-left: ${({ theme }) => theme.spacing(2)}; `; diff --git a/front/src/modules/ui/components/table/action-bar/TableActionBarButtonOpenComments.tsx b/front/src/modules/ui/components/table/action-bar/TableActionBarButtonOpenComments.tsx index 08749e0e85..f908ff700a 100644 --- a/front/src/modules/ui/components/table/action-bar/TableActionBarButtonOpenComments.tsx +++ b/front/src/modules/ui/components/table/action-bar/TableActionBarButtonOpenComments.tsx @@ -1,4 +1,4 @@ -import { IconComment } from '@/ui/icons/index'; +import { IconNotes } from '@/ui/icons/index'; import { EntityTableActionBarButton } from './EntityTableActionBarButton'; @@ -9,8 +9,8 @@ type OwnProps = { export function TableActionBarButtonToggleComments({ onClick }: OwnProps) { return ( } + label="Notes" + icon={} onClick={onClick} /> ); diff --git a/front/src/modules/ui/components/table/table-header/DropdownButton.tsx b/front/src/modules/ui/components/table/table-header/DropdownButton.tsx index a89a3cc2d6..66befd5d72 100644 --- a/front/src/modules/ui/components/table/table-header/DropdownButton.tsx +++ b/front/src/modules/ui/components/table/table-header/DropdownButton.tsx @@ -179,7 +179,7 @@ function DropdownButton({ }; const dropdownRef = useRef(null); - useOutsideAlerter(dropdownRef, onOutsideClick); + useOutsideAlerter({ ref: dropdownRef, callback: onOutsideClick }); return ( diff --git a/front/src/modules/ui/components/table/table-header/SortAndFilterBar.tsx b/front/src/modules/ui/components/table/table-header/SortAndFilterBar.tsx index cb93dd796a..c5da7a658a 100644 --- a/front/src/modules/ui/components/table/table-header/SortAndFilterBar.tsx +++ b/front/src/modules/ui/components/table/table-header/SortAndFilterBar.tsx @@ -43,7 +43,7 @@ const StyledCancelButton = styled.button` border: none; color: ${({ theme }) => theme.font.color.secondary}; cursor: pointer; - font-weight: 500; + font-weight: ${({ theme }) => theme.font.weight.medium}; margin-left: auto; margin-right: ${({ theme }) => theme.spacing(2)}; padding: ${(props) => { diff --git a/front/src/modules/ui/components/table/table-header/SortOrFilterChip.tsx b/front/src/modules/ui/components/table/table-header/SortOrFilterChip.tsx index 2ad247624e..a89da7236f 100644 --- a/front/src/modules/ui/components/table/table-header/SortOrFilterChip.tsx +++ b/front/src/modules/ui/components/table/table-header/SortOrFilterChip.tsx @@ -45,7 +45,7 @@ const StyledDelete = styled.div` `; const StyledLabelKey = styled.div` - font-weight: 500; + font-weight: ${({ theme }) => theme.font.weight.medium}; `; function SortOrFilterChip({ diff --git a/front/src/modules/ui/components/table/table-header/TableHeader.tsx b/front/src/modules/ui/components/table/table-header/TableHeader.tsx index b2507c70fd..dd9ce642b7 100644 --- a/front/src/modules/ui/components/table/table-header/TableHeader.tsx +++ b/front/src/modules/ui/components/table/table-header/TableHeader.tsx @@ -27,7 +27,7 @@ const StyledTableHeader = styled.div` color: ${({ theme }) => theme.font.color.secondary}; display: flex; flex-direction: row; - font-weight: 500; + font-weight: ${({ theme }) => theme.font.weight.medium}; height: 40px; justify-content: space-between; padding-left: ${({ theme }) => theme.spacing(3)}; @@ -49,7 +49,7 @@ const StyledViewSection = styled.div` const StyledFilters = styled.div` display: flex; - font-weight: 400; + font-weight: ${({ theme }) => theme.font.weight.regular}; gap: 2px; `; diff --git a/front/src/modules/ui/hooks/__tests__/useOutsideAlerter.test.tsx b/front/src/modules/ui/hooks/__tests__/useOutsideAlerter.test.tsx index 268d2a5079..65319c5756 100644 --- a/front/src/modules/ui/hooks/__tests__/useOutsideAlerter.test.tsx +++ b/front/src/modules/ui/hooks/__tests__/useOutsideAlerter.test.tsx @@ -7,7 +7,7 @@ const onOutsideClick = jest.fn(); function TestComponent() { const buttonRef = useRef(null); - useOutsideAlerter(buttonRef, onOutsideClick); + useOutsideAlerter({ ref: buttonRef, callback: onOutsideClick }); return (
diff --git a/front/src/modules/ui/hooks/useOutsideAlerter.ts b/front/src/modules/ui/hooks/useOutsideAlerter.ts index dff7929667..5a77591c64 100644 --- a/front/src/modules/ui/hooks/useOutsideAlerter.ts +++ b/front/src/modules/ui/hooks/useOutsideAlerter.ts @@ -1,21 +1,52 @@ import { useEffect } from 'react'; -declare type CallbackType = () => void; +export enum OutsideClickAlerterMode { + absolute = 'absolute', + dom = 'dom', +} -export function useOutsideAlerter( - ref: React.RefObject, - callback: CallbackType, -) { +type OwnProps = { + ref: React.RefObject; + callback: () => void; + mode?: OutsideClickAlerterMode; +}; + +export function useOutsideAlerter({ + ref, + mode = OutsideClickAlerterMode.dom, + callback, +}: OwnProps) { useEffect(() => { - function handleClickOutside(event: Event) { + function handleClickOutside(event: MouseEvent) { const target = event.target as HTMLButtonElement; - if (ref.current && !ref.current.contains(target)) { + + if (!ref.current) { + return; + } + + if ( + mode === OutsideClickAlerterMode.dom && + !ref.current.contains(target) + ) { callback(); } + + if (mode === OutsideClickAlerterMode.absolute) { + const { x, y, width, height } = ref.current.getBoundingClientRect(); + const { clientX, clientY } = event; + if ( + clientX < x || + clientX > x + width || + clientY < y || + clientY > y + height + ) { + callback(); + } + } } document.addEventListener('mousedown', handleClickOutside); return () => { document.removeEventListener('mousedown', handleClickOutside); }; - }, [ref, callback]); + }, [ref, callback, mode]); } diff --git a/front/src/modules/ui/icons/index.ts b/front/src/modules/ui/icons/index.ts index 94af123f82..6440036015 100644 --- a/front/src/modules/ui/icons/index.ts +++ b/front/src/modules/ui/icons/index.ts @@ -30,3 +30,6 @@ export { IconArrowUpRight } from '@tabler/icons-react'; export { IconBrandGoogle } from '@tabler/icons-react'; export { IconUpload } from '@tabler/icons-react'; export { IconFileUpload } from '@tabler/icons-react'; +export { IconChevronsRight } from '@tabler/icons-react'; +export { IconNotes } from '@tabler/icons-react'; +export { IconCirclePlus } from '@tabler/icons-react'; diff --git a/front/src/modules/ui/layout/containers/ContentContainer.tsx b/front/src/modules/ui/layout/containers/ContentContainer.tsx index 4cbb3a0f0d..3edceaea28 100644 --- a/front/src/modules/ui/layout/containers/ContentContainer.tsx +++ b/front/src/modules/ui/layout/containers/ContentContainer.tsx @@ -10,7 +10,7 @@ type OwnProps = { topMargin?: number; }; -const MainContainer = styled.div<{ topMargin: number }>` +const StyledMainContainer = styled.div<{ topMargin: number }>` background: ${({ theme }) => theme.background.noisy}; display: flex; @@ -27,27 +27,21 @@ type LeftContainerProps = { isRightDrawerOpen?: boolean; }; -const LeftContainer = styled.div` +const StyledLeftContainer = styled.div` display: flex; position: relative; - width: calc( - 100% - - ${(props) => - props.isRightDrawerOpen - ? `${props.theme.rightDrawerWidth} - ${props.theme.spacing(2)}` - : '0px'} - ); + width: 100%; `; export function ContentContainer({ children, topMargin }: OwnProps) { const [isRightDrawerOpen] = useRecoilState(isRightDrawerOpenState); return ( - - + + {children} - + - + ); } diff --git a/front/src/modules/ui/layout/navbar/NavTitle.tsx b/front/src/modules/ui/layout/navbar/NavTitle.tsx index 0f327f7ff1..2acf0bda28 100644 --- a/front/src/modules/ui/layout/navbar/NavTitle.tsx +++ b/front/src/modules/ui/layout/navbar/NavTitle.tsx @@ -8,7 +8,7 @@ const StyledTitle = styled.div` color: ${({ theme }) => theme.font.color.light}; display: flex; font-size: ${({ theme }) => theme.font.size.xs}; - font-weight: 600; + font-weight: ${({ theme }) => theme.font.weight.semiBold}; padding-bottom: ${({ theme }) => theme.spacing(2)}; padding-left: ${({ theme }) => theme.spacing(1)}; padding-top: ${({ theme }) => theme.spacing(8)}; diff --git a/front/src/modules/ui/layout/navbar/NavWorkspaceButton.tsx b/front/src/modules/ui/layout/navbar/NavWorkspaceButton.tsx index 832e198640..1c266b98cb 100644 --- a/front/src/modules/ui/layout/navbar/NavWorkspaceButton.tsx +++ b/front/src/modules/ui/layout/navbar/NavWorkspaceButton.tsx @@ -42,7 +42,7 @@ const StyledName = styled.div` color: ${({ theme }) => theme.font.color.primary}; font-family: 'Inter'; font-size: ${({ theme }) => theme.font.size.md}; - font-weight: 500; + font-weight: ${({ theme }) => theme.font.weight.medium}; margin-left: ${({ theme }) => theme.spacing(2)}; `; diff --git a/front/src/modules/ui/layout/right-drawer/components/RightDrawer.tsx b/front/src/modules/ui/layout/right-drawer/components/RightDrawer.tsx index 4589e9d3dc..77443ad5b3 100644 --- a/front/src/modules/ui/layout/right-drawer/components/RightDrawer.tsx +++ b/front/src/modules/ui/layout/right-drawer/components/RightDrawer.tsx @@ -1,33 +1,61 @@ +import { useRef } from 'react'; import styled from '@emotion/styled'; import { useRecoilState } from 'recoil'; +import { + OutsideClickAlerterMode, + useOutsideAlerter, +} from '@/ui/hooks/useOutsideAlerter'; import { isDefined } from '@/utils/type-guards/isDefined'; -import { Panel } from '../../Panel'; import { isRightDrawerOpenState } from '../states/isRightDrawerOpenState'; import { rightDrawerPageState } from '../states/rightDrawerPageState'; import { RightDrawerRouter } from './RightDrawerRouter'; +const StyledContainer = styled.div` + background: ${({ theme }) => theme.background.primary}; + box-shadow: ${({ theme }) => theme.boxShadow.strong}; + height: 100%; + overflow-x: hidden; + position: fixed; + right: 0; + top: 0; + transition: width 0.5s; + width: ${({ theme }) => theme.rightDrawerWidth}; + z-index: 2; +`; + const StyledRightDrawer = styled.div` display: flex; flex-direction: row; - width: ${({ theme }) => theme.rightDrawerWidth}; + width: 100%; `; export function RightDrawer() { - const [isRightDrawerOpen] = useRecoilState(isRightDrawerOpenState); + const [isRightDrawerOpen, setIsRightDrawerOpen] = useRecoilState( + isRightDrawerOpenState, + ); + const [rightDrawerPage] = useRecoilState(rightDrawerPageState); + const rightDrawerRef = useRef(null); + useOutsideAlerter({ + ref: rightDrawerRef, + callback: () => setIsRightDrawerOpen(false), + mode: OutsideClickAlerterMode.absolute, + }); if (!isRightDrawerOpen || !isDefined(rightDrawerPage)) { return <>; } return ( - - - - - + <> + + + + + + ); } diff --git a/front/src/modules/ui/layout/right-drawer/components/RightDrawerBody.tsx b/front/src/modules/ui/layout/right-drawer/components/RightDrawerBody.tsx index 3abc1b49f2..53136df767 100644 --- a/front/src/modules/ui/layout/right-drawer/components/RightDrawerBody.tsx +++ b/front/src/modules/ui/layout/right-drawer/components/RightDrawerBody.tsx @@ -3,5 +3,7 @@ import styled from '@emotion/styled'; export const RightDrawerBody = styled.div` display: flex; flex-direction: column; + height: calc(100vh - ${({ theme }) => theme.spacing(10)}); overflow: auto; + position: relative; `; diff --git a/front/src/modules/ui/layout/right-drawer/components/RightDrawerRouter.tsx b/front/src/modules/ui/layout/right-drawer/components/RightDrawerRouter.tsx index b7dafb48b5..a487bedd4b 100644 --- a/front/src/modules/ui/layout/right-drawer/components/RightDrawerRouter.tsx +++ b/front/src/modules/ui/layout/right-drawer/components/RightDrawerRouter.tsx @@ -1,10 +1,12 @@ import { useRecoilState } from 'recoil'; -import { RightDrawerComments } from '@/comments/components/RightDrawerComments'; -import { RightDrawerCreateCommentThread } from '@/comments/components/RightDrawerCreateCommentThread'; +import { RightDrawerCreateCommentThread } from '@/comments/components/right-drawer/create/RightDrawerCreateCommentThread'; +import { RightDrawerEditCommentThread } from '@/comments/components/right-drawer/edit/RightDrawerEditCommentThread'; +import { RightDrawerTimeline } from '@/comments/components/right-drawer/RightDrawerTimeline'; import { isDefined } from '@/utils/type-guards/isDefined'; import { rightDrawerPageState } from '../states/rightDrawerPageState'; +import { RightDrawerPages } from '../types/RightDrawerPages'; export function RightDrawerRouter() { const [rightDrawerPage] = useRecoilState(rightDrawerPageState); @@ -13,11 +15,14 @@ export function RightDrawerRouter() { return <>; } - return rightDrawerPage === 'comments' ? ( - - ) : rightDrawerPage === 'create-comment-thread' ? ( - - ) : ( - <> - ); + switch (rightDrawerPage) { + case RightDrawerPages.Timeline: + return ; + case RightDrawerPages.CreateCommentThread: + return ; + case RightDrawerPages.EditCommentThread: + return ; + default: + return <>; + } } diff --git a/front/src/modules/ui/layout/right-drawer/components/RightDrawerTopBar.tsx b/front/src/modules/ui/layout/right-drawer/components/RightDrawerTopBar.tsx index 2eb4ea6506..d9ce8a2c97 100644 --- a/front/src/modules/ui/layout/right-drawer/components/RightDrawerTopBar.tsx +++ b/front/src/modules/ui/layout/right-drawer/components/RightDrawerTopBar.tsx @@ -1,5 +1,7 @@ import styled from '@emotion/styled'; +import { Button } from '@/ui/components/buttons/Button'; + import { RightDrawerTopBarCloseButton } from './RightDrawerTopBarCloseButton'; const StyledRightDrawerTopBar = styled.div` @@ -8,7 +10,7 @@ const StyledRightDrawerTopBar = styled.div` color: ${({ theme }) => theme.font.color.secondary}; display: flex; flex-direction: row; - font-size: 13px; + font-size: ${({ theme }) => theme.font.size.md}; justify-content: space-between; min-height: 40px; padding-left: 8px; @@ -17,19 +19,24 @@ const StyledRightDrawerTopBar = styled.div` const StyledTopBarTitle = styled.div` align-items: center; - font-weight: 500; + font-weight: ${({ theme }) => theme.font.weight.medium}; margin-right: ${({ theme }) => theme.spacing(1)}; `; -export function RightDrawerTopBar({ - title, -}: { +type OwnProps = { title: string | null | undefined; -}) { + onSave?: () => void; +}; + +export function RightDrawerTopBar({ title, onSave }: OwnProps) { + function handleOnClick() { + onSave?.(); + } return ( - {title} + {title} + {onSave &&