mirror of
https://github.com/twentyhq/twenty.git
synced 2024-11-26 04:17:15 +03:00
Add "show company / people" view and "Notes" concept (#528)
* Begin adding show view and refactoring threads to become notes
* Progress on design
* Progress redesign timeline
* Dropdown button, design improvement
* Open comment thread edit mode in drawer
* Autosave local storage and commentThreadcount
* Improve display and fix missing key issue
* Remove some hardcoded CSS properties
* Create button
* Split company show into ui/business + fix eslint
* Fix font weight
* Begin auto-save on edit mode
* Save server-side query result to Apollo cache
* Fix save behavior
* Refetch timeline after creating note
* Rename createCommentThreadWithComment
* Improve styling
* Revert "Improve styling"
This reverts commit 9fbbf2db00
.
* Improve CSS styling
* Bring back border radius inadvertently removed
* padding adjustment
* Improve blocknote design
* Improve edit mode display
* Remove Comments.tsx
* Remove irrelevant comment stories
* Removed un-necessary panel component
* stop using fragment, move trash icon
* Add a basic story for CompanyShow
* Add a basic People show view
* Fix storybook tests
* Add very basic Person story
* Refactor PR1
* Refactor part 2
* Refactor part 3
* Refactor part 4
* Fix tests
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
parent
ca180acf9f
commit
94a913a41f
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -31,6 +31,7 @@
|
||||
"!javascript",
|
||||
"!json",
|
||||
"!typescript",
|
||||
"!typescriptreact",
|
||||
"md",
|
||||
"mdx"
|
||||
],
|
||||
|
@ -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: {
|
||||
|
@ -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",
|
||||
|
@ -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() {
|
||||
<Routes>
|
||||
<Route path="" element={<Navigate to="/people" replace />} />
|
||||
<Route path="people" element={<People />} />
|
||||
<Route path="person/:personId" element={<PersonShow />} />
|
||||
<Route path="companies" element={<Companies />} />
|
||||
<Route
|
||||
path="companies/:companyId"
|
||||
element={<CompanyShow />}
|
||||
/>
|
||||
|
||||
<Route path="opportunities" element={<Opportunities />} />
|
||||
<Route
|
||||
path="settings/*"
|
||||
|
@ -206,24 +206,47 @@ export type CommentScalarWhereInput = {
|
||||
|
||||
export type CommentThread = {
|
||||
__typename?: 'CommentThread';
|
||||
author: User;
|
||||
authorId: Scalars['String'];
|
||||
body?: Maybe<Scalars['String']>;
|
||||
commentThreadTargets?: Maybe<Array<CommentThreadTarget>>;
|
||||
comments?: Maybe<Array<Comment>>;
|
||||
createdAt: Scalars['DateTime'];
|
||||
id: Scalars['ID'];
|
||||
title?: Maybe<Scalars['String']>;
|
||||
updatedAt: Scalars['DateTime'];
|
||||
};
|
||||
|
||||
export type CommentThreadCreateInput = {
|
||||
author: UserCreateNestedOneWithoutCommentThreadInput;
|
||||
body?: InputMaybe<Scalars['String']>;
|
||||
commentThreadTargets?: InputMaybe<CommentThreadTargetCreateNestedManyWithoutCommentThreadInput>;
|
||||
comments?: InputMaybe<CommentCreateNestedManyWithoutCommentThreadInput>;
|
||||
createdAt?: InputMaybe<Scalars['DateTime']>;
|
||||
id?: InputMaybe<Scalars['String']>;
|
||||
title?: InputMaybe<Scalars['String']>;
|
||||
updatedAt?: InputMaybe<Scalars['DateTime']>;
|
||||
};
|
||||
|
||||
export type CommentThreadCreateManyWorkspaceInput = {
|
||||
export type CommentThreadCreateManyAuthorInput = {
|
||||
body?: InputMaybe<Scalars['String']>;
|
||||
createdAt?: InputMaybe<Scalars['DateTime']>;
|
||||
id?: InputMaybe<Scalars['String']>;
|
||||
title?: InputMaybe<Scalars['String']>;
|
||||
updatedAt?: InputMaybe<Scalars['DateTime']>;
|
||||
};
|
||||
|
||||
export type CommentThreadCreateManyAuthorInputEnvelope = {
|
||||
data: Array<CommentThreadCreateManyAuthorInput>;
|
||||
skipDuplicates?: InputMaybe<Scalars['Boolean']>;
|
||||
};
|
||||
|
||||
export type CommentThreadCreateManyWorkspaceInput = {
|
||||
authorId: Scalars['String'];
|
||||
body?: InputMaybe<Scalars['String']>;
|
||||
createdAt?: InputMaybe<Scalars['DateTime']>;
|
||||
id?: InputMaybe<Scalars['String']>;
|
||||
title?: InputMaybe<Scalars['String']>;
|
||||
updatedAt?: InputMaybe<Scalars['DateTime']>;
|
||||
};
|
||||
|
||||
@ -232,10 +255,22 @@ export type CommentThreadCreateManyWorkspaceInputEnvelope = {
|
||||
skipDuplicates?: InputMaybe<Scalars['Boolean']>;
|
||||
};
|
||||
|
||||
export type CommentThreadCreateNestedManyWithoutAuthorInput = {
|
||||
connect?: InputMaybe<Array<CommentThreadWhereUniqueInput>>;
|
||||
connectOrCreate?: InputMaybe<Array<CommentThreadCreateOrConnectWithoutAuthorInput>>;
|
||||
create?: InputMaybe<Array<CommentThreadCreateWithoutAuthorInput>>;
|
||||
createMany?: InputMaybe<CommentThreadCreateManyAuthorInputEnvelope>;
|
||||
};
|
||||
|
||||
export type CommentThreadCreateNestedOneWithoutCommentsInput = {
|
||||
connect?: InputMaybe<CommentThreadWhereUniqueInput>;
|
||||
};
|
||||
|
||||
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<CommentThreadTargetCreateNestedManyWithoutCommentThreadInput>;
|
||||
createdAt?: InputMaybe<Scalars['DateTime']>;
|
||||
id?: InputMaybe<Scalars['String']>;
|
||||
updatedAt?: InputMaybe<Scalars['DateTime']>;
|
||||
};
|
||||
|
||||
export type CommentThreadCreateWithoutWorkspaceInput = {
|
||||
export type CommentThreadCreateWithoutAuthorInput = {
|
||||
body?: InputMaybe<Scalars['String']>;
|
||||
commentThreadTargets?: InputMaybe<CommentThreadTargetCreateNestedManyWithoutCommentThreadInput>;
|
||||
comments?: InputMaybe<CommentCreateNestedManyWithoutCommentThreadInput>;
|
||||
createdAt?: InputMaybe<Scalars['DateTime']>;
|
||||
id?: InputMaybe<Scalars['String']>;
|
||||
title?: InputMaybe<Scalars['String']>;
|
||||
updatedAt?: InputMaybe<Scalars['DateTime']>;
|
||||
};
|
||||
|
||||
export type CommentThreadCreateWithoutCommentsInput = {
|
||||
author: UserCreateNestedOneWithoutCommentThreadInput;
|
||||
body?: InputMaybe<Scalars['String']>;
|
||||
commentThreadTargets?: InputMaybe<CommentThreadTargetCreateNestedManyWithoutCommentThreadInput>;
|
||||
createdAt?: InputMaybe<Scalars['DateTime']>;
|
||||
id?: InputMaybe<Scalars['String']>;
|
||||
title?: InputMaybe<Scalars['String']>;
|
||||
updatedAt?: InputMaybe<Scalars['DateTime']>;
|
||||
};
|
||||
|
||||
export type CommentThreadCreateWithoutWorkspaceInput = {
|
||||
author: UserCreateNestedOneWithoutCommentThreadInput;
|
||||
body?: InputMaybe<Scalars['String']>;
|
||||
commentThreadTargets?: InputMaybe<CommentThreadTargetCreateNestedManyWithoutCommentThreadInput>;
|
||||
comments?: InputMaybe<CommentCreateNestedManyWithoutCommentThreadInput>;
|
||||
createdAt?: InputMaybe<Scalars['DateTime']>;
|
||||
id?: InputMaybe<Scalars['String']>;
|
||||
title?: InputMaybe<Scalars['String']>;
|
||||
updatedAt?: InputMaybe<Scalars['DateTime']>;
|
||||
};
|
||||
|
||||
export type CommentThreadListRelationFilter = {
|
||||
every?: InputMaybe<CommentThreadWhereInput>;
|
||||
none?: InputMaybe<CommentThreadWhereInput>;
|
||||
some?: InputMaybe<CommentThreadWhereInput>;
|
||||
};
|
||||
|
||||
export type CommentThreadOrderByRelationAggregateInput = {
|
||||
_count?: InputMaybe<SortOrder>;
|
||||
};
|
||||
|
||||
export type CommentThreadOrderByWithRelationInput = {
|
||||
author?: InputMaybe<UserOrderByWithRelationInput>;
|
||||
authorId?: InputMaybe<SortOrder>;
|
||||
body?: InputMaybe<SortOrder>;
|
||||
commentThreadTargets?: InputMaybe<CommentThreadTargetOrderByRelationAggregateInput>;
|
||||
comments?: InputMaybe<CommentOrderByRelationAggregateInput>;
|
||||
createdAt?: InputMaybe<SortOrder>;
|
||||
id?: InputMaybe<SortOrder>;
|
||||
title?: InputMaybe<SortOrder>;
|
||||
updatedAt?: InputMaybe<SortOrder>;
|
||||
};
|
||||
|
||||
@ -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<Array<CommentThreadScalarWhereInput>>;
|
||||
NOT?: InputMaybe<Array<CommentThreadScalarWhereInput>>;
|
||||
OR?: InputMaybe<Array<CommentThreadScalarWhereInput>>;
|
||||
authorId?: InputMaybe<StringFilter>;
|
||||
body?: InputMaybe<StringNullableFilter>;
|
||||
createdAt?: InputMaybe<DateTimeFilter>;
|
||||
id?: InputMaybe<StringFilter>;
|
||||
title?: InputMaybe<StringNullableFilter>;
|
||||
updatedAt?: InputMaybe<DateTimeFilter>;
|
||||
};
|
||||
|
||||
@ -418,24 +489,48 @@ export type CommentThreadTargetWhereUniqueInput = {
|
||||
};
|
||||
|
||||
export type CommentThreadUpdateInput = {
|
||||
author?: InputMaybe<UserUpdateOneRequiredWithoutCommentThreadNestedInput>;
|
||||
body?: InputMaybe<NullableStringFieldUpdateOperationsInput>;
|
||||
commentThreadTargets?: InputMaybe<CommentThreadTargetUpdateManyWithoutCommentThreadNestedInput>;
|
||||
comments?: InputMaybe<CommentUpdateManyWithoutCommentThreadNestedInput>;
|
||||
createdAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
|
||||
id?: InputMaybe<StringFieldUpdateOperationsInput>;
|
||||
title?: InputMaybe<NullableStringFieldUpdateOperationsInput>;
|
||||
updatedAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
|
||||
};
|
||||
|
||||
export type CommentThreadUpdateManyMutationInput = {
|
||||
body?: InputMaybe<NullableStringFieldUpdateOperationsInput>;
|
||||
createdAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
|
||||
id?: InputMaybe<StringFieldUpdateOperationsInput>;
|
||||
title?: InputMaybe<NullableStringFieldUpdateOperationsInput>;
|
||||
updatedAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
|
||||
};
|
||||
|
||||
export type CommentThreadUpdateManyWithWhereWithoutAuthorInput = {
|
||||
data: CommentThreadUpdateManyMutationInput;
|
||||
where: CommentThreadScalarWhereInput;
|
||||
};
|
||||
|
||||
export type CommentThreadUpdateManyWithWhereWithoutWorkspaceInput = {
|
||||
data: CommentThreadUpdateManyMutationInput;
|
||||
where: CommentThreadScalarWhereInput;
|
||||
};
|
||||
|
||||
export type CommentThreadUpdateManyWithoutAuthorNestedInput = {
|
||||
connect?: InputMaybe<Array<CommentThreadWhereUniqueInput>>;
|
||||
connectOrCreate?: InputMaybe<Array<CommentThreadCreateOrConnectWithoutAuthorInput>>;
|
||||
create?: InputMaybe<Array<CommentThreadCreateWithoutAuthorInput>>;
|
||||
createMany?: InputMaybe<CommentThreadCreateManyAuthorInputEnvelope>;
|
||||
delete?: InputMaybe<Array<CommentThreadWhereUniqueInput>>;
|
||||
deleteMany?: InputMaybe<Array<CommentThreadScalarWhereInput>>;
|
||||
disconnect?: InputMaybe<Array<CommentThreadWhereUniqueInput>>;
|
||||
set?: InputMaybe<Array<CommentThreadWhereUniqueInput>>;
|
||||
update?: InputMaybe<Array<CommentThreadUpdateWithWhereUniqueWithoutAuthorInput>>;
|
||||
updateMany?: InputMaybe<Array<CommentThreadUpdateManyWithWhereWithoutAuthorInput>>;
|
||||
upsert?: InputMaybe<Array<CommentThreadUpsertWithWhereUniqueWithoutAuthorInput>>;
|
||||
};
|
||||
|
||||
export type CommentThreadUpdateManyWithoutWorkspaceNestedInput = {
|
||||
connect?: InputMaybe<Array<CommentThreadWhereUniqueInput>>;
|
||||
connectOrCreate?: InputMaybe<Array<CommentThreadCreateOrConnectWithoutWorkspaceInput>>;
|
||||
@ -458,26 +553,53 @@ export type CommentThreadUpdateOneRequiredWithoutCommentsNestedInput = {
|
||||
upsert?: InputMaybe<CommentThreadUpsertWithoutCommentsInput>;
|
||||
};
|
||||
|
||||
export type CommentThreadUpdateWithWhereUniqueWithoutAuthorInput = {
|
||||
data: CommentThreadUpdateWithoutAuthorInput;
|
||||
where: CommentThreadWhereUniqueInput;
|
||||
};
|
||||
|
||||
export type CommentThreadUpdateWithWhereUniqueWithoutWorkspaceInput = {
|
||||
data: CommentThreadUpdateWithoutWorkspaceInput;
|
||||
where: CommentThreadWhereUniqueInput;
|
||||
};
|
||||
|
||||
export type CommentThreadUpdateWithoutCommentsInput = {
|
||||
commentThreadTargets?: InputMaybe<CommentThreadTargetUpdateManyWithoutCommentThreadNestedInput>;
|
||||
createdAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
|
||||
id?: InputMaybe<StringFieldUpdateOperationsInput>;
|
||||
updatedAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
|
||||
};
|
||||
|
||||
export type CommentThreadUpdateWithoutWorkspaceInput = {
|
||||
export type CommentThreadUpdateWithoutAuthorInput = {
|
||||
body?: InputMaybe<NullableStringFieldUpdateOperationsInput>;
|
||||
commentThreadTargets?: InputMaybe<CommentThreadTargetUpdateManyWithoutCommentThreadNestedInput>;
|
||||
comments?: InputMaybe<CommentUpdateManyWithoutCommentThreadNestedInput>;
|
||||
createdAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
|
||||
id?: InputMaybe<StringFieldUpdateOperationsInput>;
|
||||
title?: InputMaybe<NullableStringFieldUpdateOperationsInput>;
|
||||
updatedAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
|
||||
};
|
||||
|
||||
export type CommentThreadUpdateWithoutCommentsInput = {
|
||||
author?: InputMaybe<UserUpdateOneRequiredWithoutCommentThreadNestedInput>;
|
||||
body?: InputMaybe<NullableStringFieldUpdateOperationsInput>;
|
||||
commentThreadTargets?: InputMaybe<CommentThreadTargetUpdateManyWithoutCommentThreadNestedInput>;
|
||||
createdAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
|
||||
id?: InputMaybe<StringFieldUpdateOperationsInput>;
|
||||
title?: InputMaybe<NullableStringFieldUpdateOperationsInput>;
|
||||
updatedAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
|
||||
};
|
||||
|
||||
export type CommentThreadUpdateWithoutWorkspaceInput = {
|
||||
author?: InputMaybe<UserUpdateOneRequiredWithoutCommentThreadNestedInput>;
|
||||
body?: InputMaybe<NullableStringFieldUpdateOperationsInput>;
|
||||
commentThreadTargets?: InputMaybe<CommentThreadTargetUpdateManyWithoutCommentThreadNestedInput>;
|
||||
comments?: InputMaybe<CommentUpdateManyWithoutCommentThreadNestedInput>;
|
||||
createdAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
|
||||
id?: InputMaybe<StringFieldUpdateOperationsInput>;
|
||||
title?: InputMaybe<NullableStringFieldUpdateOperationsInput>;
|
||||
updatedAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
|
||||
};
|
||||
|
||||
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<Array<CommentThreadWhereInput>>;
|
||||
NOT?: InputMaybe<Array<CommentThreadWhereInput>>;
|
||||
OR?: InputMaybe<Array<CommentThreadWhereInput>>;
|
||||
author?: InputMaybe<UserRelationFilter>;
|
||||
authorId?: InputMaybe<StringFilter>;
|
||||
body?: InputMaybe<StringNullableFilter>;
|
||||
commentThreadTargets?: InputMaybe<CommentThreadTargetListRelationFilter>;
|
||||
comments?: InputMaybe<CommentListRelationFilter>;
|
||||
createdAt?: InputMaybe<DateTimeFilter>;
|
||||
id?: InputMaybe<StringFilter>;
|
||||
title?: InputMaybe<StringNullableFilter>;
|
||||
updatedAt?: InputMaybe<DateTimeFilter>;
|
||||
};
|
||||
|
||||
@ -651,7 +777,7 @@ export enum CommentableType {
|
||||
|
||||
export type Company = {
|
||||
__typename?: 'Company';
|
||||
_commentCount: Scalars['Int'];
|
||||
_commentThreadCount: Scalars['Int'];
|
||||
accountOwner?: Maybe<User>;
|
||||
accountOwnerId?: Maybe<Scalars['String']>;
|
||||
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<CommentThread>;
|
||||
comments: Array<Comment>;
|
||||
company?: Maybe<Company>;
|
||||
companyId?: Maybe<Scalars['String']>;
|
||||
createdAt: Scalars['DateTime'];
|
||||
displayName: Scalars['String'];
|
||||
email: Scalars['String'];
|
||||
firstName: Scalars['String'];
|
||||
id: Scalars['ID'];
|
||||
@ -2303,6 +2430,8 @@ export type Query = {
|
||||
findManyPipelineProgress: Array<PipelineProgress>;
|
||||
findManyPipelineStage: Array<PipelineStage>;
|
||||
findManyUser: Array<User>;
|
||||
findUniqueCompany: Company;
|
||||
findUniquePerson: Person;
|
||||
};
|
||||
|
||||
|
||||
@ -2380,6 +2509,16 @@ export type QueryFindManyUserArgs = {
|
||||
where?: InputMaybe<UserWhereInput>;
|
||||
};
|
||||
|
||||
|
||||
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<Array<CommentThread>>;
|
||||
avatarUrl?: Maybe<Scalars['String']>;
|
||||
comments?: Maybe<Array<Comment>>;
|
||||
companies?: Maybe<Array<Company>>;
|
||||
@ -2451,6 +2591,12 @@ export type User = {
|
||||
workspaceMember?: Maybe<WorkspaceMember>;
|
||||
};
|
||||
|
||||
export type UserCreateNestedOneWithoutCommentThreadInput = {
|
||||
connect?: InputMaybe<UserWhereUniqueInput>;
|
||||
connectOrCreate?: InputMaybe<UserCreateOrConnectWithoutCommentThreadInput>;
|
||||
create?: InputMaybe<UserCreateWithoutCommentThreadInput>;
|
||||
};
|
||||
|
||||
export type UserCreateNestedOneWithoutCommentsInput = {
|
||||
connect?: InputMaybe<UserWhereUniqueInput>;
|
||||
};
|
||||
@ -2465,6 +2611,11 @@ export type UserCreateNestedOneWithoutWorkspaceMemberInput = {
|
||||
create?: InputMaybe<UserCreateWithoutWorkspaceMemberInput>;
|
||||
};
|
||||
|
||||
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<Scalars['String']>;
|
||||
comments?: InputMaybe<CommentCreateNestedManyWithoutAuthorInput>;
|
||||
companies?: InputMaybe<CompanyCreateNestedManyWithoutAccountOwnerInput>;
|
||||
createdAt?: InputMaybe<Scalars['DateTime']>;
|
||||
disabled?: InputMaybe<Scalars['Boolean']>;
|
||||
email: Scalars['String'];
|
||||
emailVerified?: InputMaybe<Scalars['Boolean']>;
|
||||
firstName?: InputMaybe<Scalars['String']>;
|
||||
id?: InputMaybe<Scalars['String']>;
|
||||
lastName?: InputMaybe<Scalars['String']>;
|
||||
lastSeen?: InputMaybe<Scalars['DateTime']>;
|
||||
locale: Scalars['String'];
|
||||
metadata?: InputMaybe<Scalars['JSON']>;
|
||||
phoneNumber?: InputMaybe<Scalars['String']>;
|
||||
updatedAt?: InputMaybe<Scalars['DateTime']>;
|
||||
};
|
||||
|
||||
export type UserCreateWithoutCommentsInput = {
|
||||
CommentThread?: InputMaybe<CommentThreadCreateNestedManyWithoutAuthorInput>;
|
||||
avatarUrl?: InputMaybe<Scalars['String']>;
|
||||
companies?: InputMaybe<CompanyCreateNestedManyWithoutAccountOwnerInput>;
|
||||
createdAt?: InputMaybe<Scalars['DateTime']>;
|
||||
@ -2493,6 +2663,7 @@ export type UserCreateWithoutCommentsInput = {
|
||||
};
|
||||
|
||||
export type UserCreateWithoutWorkspaceMemberInput = {
|
||||
CommentThread?: InputMaybe<CommentThreadCreateNestedManyWithoutAuthorInput>;
|
||||
avatarUrl?: InputMaybe<Scalars['String']>;
|
||||
comments?: InputMaybe<CommentCreateNestedManyWithoutAuthorInput>;
|
||||
companies?: InputMaybe<CompanyCreateNestedManyWithoutAccountOwnerInput>;
|
||||
@ -2516,6 +2687,7 @@ export type UserExists = {
|
||||
};
|
||||
|
||||
export type UserOrderByWithRelationInput = {
|
||||
CommentThread?: InputMaybe<CommentThreadOrderByRelationAggregateInput>;
|
||||
avatarUrl?: InputMaybe<SortOrder>;
|
||||
comments?: InputMaybe<CommentOrderByRelationAggregateInput>;
|
||||
companies?: InputMaybe<CompanyOrderByRelationAggregateInput>;
|
||||
@ -2557,6 +2729,7 @@ export enum UserScalarFieldEnum {
|
||||
}
|
||||
|
||||
export type UserUpdateInput = {
|
||||
CommentThread?: InputMaybe<CommentThreadUpdateManyWithoutAuthorNestedInput>;
|
||||
avatarUrl?: InputMaybe<NullableStringFieldUpdateOperationsInput>;
|
||||
comments?: InputMaybe<CommentUpdateManyWithoutAuthorNestedInput>;
|
||||
companies?: InputMaybe<CompanyUpdateManyWithoutAccountOwnerNestedInput>;
|
||||
@ -2574,6 +2747,14 @@ export type UserUpdateInput = {
|
||||
updatedAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
|
||||
};
|
||||
|
||||
export type UserUpdateOneRequiredWithoutCommentThreadNestedInput = {
|
||||
connect?: InputMaybe<UserWhereUniqueInput>;
|
||||
connectOrCreate?: InputMaybe<UserCreateOrConnectWithoutCommentThreadInput>;
|
||||
create?: InputMaybe<UserCreateWithoutCommentThreadInput>;
|
||||
update?: InputMaybe<UserUpdateWithoutCommentThreadInput>;
|
||||
upsert?: InputMaybe<UserUpsertWithoutCommentThreadInput>;
|
||||
};
|
||||
|
||||
export type UserUpdateOneRequiredWithoutCommentsNestedInput = {
|
||||
connect?: InputMaybe<UserWhereUniqueInput>;
|
||||
connectOrCreate?: InputMaybe<UserCreateOrConnectWithoutCommentsInput>;
|
||||
@ -2594,24 +2775,7 @@ export type UserUpdateOneWithoutCompaniesNestedInput = {
|
||||
connect?: InputMaybe<UserWhereUniqueInput>;
|
||||
};
|
||||
|
||||
export type UserUpdateWithoutCommentsInput = {
|
||||
avatarUrl?: InputMaybe<NullableStringFieldUpdateOperationsInput>;
|
||||
companies?: InputMaybe<CompanyUpdateManyWithoutAccountOwnerNestedInput>;
|
||||
createdAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
|
||||
disabled?: InputMaybe<BoolFieldUpdateOperationsInput>;
|
||||
email?: InputMaybe<StringFieldUpdateOperationsInput>;
|
||||
emailVerified?: InputMaybe<BoolFieldUpdateOperationsInput>;
|
||||
firstName?: InputMaybe<NullableStringFieldUpdateOperationsInput>;
|
||||
id?: InputMaybe<StringFieldUpdateOperationsInput>;
|
||||
lastName?: InputMaybe<NullableStringFieldUpdateOperationsInput>;
|
||||
lastSeen?: InputMaybe<NullableDateTimeFieldUpdateOperationsInput>;
|
||||
locale?: InputMaybe<StringFieldUpdateOperationsInput>;
|
||||
metadata?: InputMaybe<Scalars['JSON']>;
|
||||
phoneNumber?: InputMaybe<NullableStringFieldUpdateOperationsInput>;
|
||||
updatedAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
|
||||
};
|
||||
|
||||
export type UserUpdateWithoutWorkspaceMemberInput = {
|
||||
export type UserUpdateWithoutCommentThreadInput = {
|
||||
avatarUrl?: InputMaybe<NullableStringFieldUpdateOperationsInput>;
|
||||
comments?: InputMaybe<CommentUpdateManyWithoutAuthorNestedInput>;
|
||||
companies?: InputMaybe<CompanyUpdateManyWithoutAccountOwnerNestedInput>;
|
||||
@ -2629,6 +2793,48 @@ export type UserUpdateWithoutWorkspaceMemberInput = {
|
||||
updatedAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
|
||||
};
|
||||
|
||||
export type UserUpdateWithoutCommentsInput = {
|
||||
CommentThread?: InputMaybe<CommentThreadUpdateManyWithoutAuthorNestedInput>;
|
||||
avatarUrl?: InputMaybe<NullableStringFieldUpdateOperationsInput>;
|
||||
companies?: InputMaybe<CompanyUpdateManyWithoutAccountOwnerNestedInput>;
|
||||
createdAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
|
||||
disabled?: InputMaybe<BoolFieldUpdateOperationsInput>;
|
||||
email?: InputMaybe<StringFieldUpdateOperationsInput>;
|
||||
emailVerified?: InputMaybe<BoolFieldUpdateOperationsInput>;
|
||||
firstName?: InputMaybe<NullableStringFieldUpdateOperationsInput>;
|
||||
id?: InputMaybe<StringFieldUpdateOperationsInput>;
|
||||
lastName?: InputMaybe<NullableStringFieldUpdateOperationsInput>;
|
||||
lastSeen?: InputMaybe<NullableDateTimeFieldUpdateOperationsInput>;
|
||||
locale?: InputMaybe<StringFieldUpdateOperationsInput>;
|
||||
metadata?: InputMaybe<Scalars['JSON']>;
|
||||
phoneNumber?: InputMaybe<NullableStringFieldUpdateOperationsInput>;
|
||||
updatedAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
|
||||
};
|
||||
|
||||
export type UserUpdateWithoutWorkspaceMemberInput = {
|
||||
CommentThread?: InputMaybe<CommentThreadUpdateManyWithoutAuthorNestedInput>;
|
||||
avatarUrl?: InputMaybe<NullableStringFieldUpdateOperationsInput>;
|
||||
comments?: InputMaybe<CommentUpdateManyWithoutAuthorNestedInput>;
|
||||
companies?: InputMaybe<CompanyUpdateManyWithoutAccountOwnerNestedInput>;
|
||||
createdAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
|
||||
disabled?: InputMaybe<BoolFieldUpdateOperationsInput>;
|
||||
email?: InputMaybe<StringFieldUpdateOperationsInput>;
|
||||
emailVerified?: InputMaybe<BoolFieldUpdateOperationsInput>;
|
||||
firstName?: InputMaybe<NullableStringFieldUpdateOperationsInput>;
|
||||
id?: InputMaybe<StringFieldUpdateOperationsInput>;
|
||||
lastName?: InputMaybe<NullableStringFieldUpdateOperationsInput>;
|
||||
lastSeen?: InputMaybe<NullableDateTimeFieldUpdateOperationsInput>;
|
||||
locale?: InputMaybe<StringFieldUpdateOperationsInput>;
|
||||
metadata?: InputMaybe<Scalars['JSON']>;
|
||||
phoneNumber?: InputMaybe<NullableStringFieldUpdateOperationsInput>;
|
||||
updatedAt?: InputMaybe<DateTimeFieldUpdateOperationsInput>;
|
||||
};
|
||||
|
||||
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<Array<UserWhereInput>>;
|
||||
CommentThread?: InputMaybe<CommentThreadListRelationFilter>;
|
||||
NOT?: InputMaybe<Array<UserWhereInput>>;
|
||||
OR?: InputMaybe<Array<UserWhereInput>>;
|
||||
avatarUrl?: InputMaybe<StringNullableFilter>;
|
||||
@ -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<Scalars['String']>;
|
||||
title?: InputMaybe<Scalars['String']>;
|
||||
authorId: Scalars['String'];
|
||||
createdAt: Scalars['DateTime'];
|
||||
commentId: Scalars['String'];
|
||||
commentThreadTargetArray: Array<CommentThreadTargetCreateManyCommentThreadInput> | 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']> | 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<Scalars['String']>;
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateCommentThreadTitleMutation = { __typename?: 'Mutation', updateOneCommentThread: { __typename?: 'CommentThread', id: string, title?: string | null } };
|
||||
|
||||
export type UpdateCommentThreadBodyMutationVariables = Exact<{
|
||||
commentThreadId: Scalars['String'];
|
||||
commentThreadBody?: InputMaybe<Scalars['String']>;
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateCommentThreadBodyMutation = { __typename?: 'Mutation', updateOneCommentThread: { __typename?: 'CommentThread', id: string, body?: string | null } };
|
||||
|
||||
export type GetCompaniesQueryVariables = Exact<{
|
||||
orderBy?: InputMaybe<Array<CompanyOrderByWithRelationInput> | CompanyOrderByWithRelationInput>;
|
||||
where?: InputMaybe<CompanyWhereInput>;
|
||||
}>;
|
||||
|
||||
|
||||
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<Scalars['String']>;
|
||||
@ -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<Scalars['String']>;
|
||||
@ -3416,14 +3653,15 @@ export function useCreateCommentMutation(baseOptions?: Apollo.MutationHookOption
|
||||
export type CreateCommentMutationHookResult = ReturnType<typeof useCreateCommentMutation>;
|
||||
export type CreateCommentMutationResult = Apollo.MutationResult<CreateCommentMutation>;
|
||||
export type CreateCommentMutationOptions = Apollo.BaseMutationOptions<CreateCommentMutation, CreateCommentMutationVariables>;
|
||||
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<CreateCommentThreadWithCommentMutation, CreateCommentThreadWithCommentMutationVariables>;
|
||||
export type CreateCommentThreadMutationFn = Apollo.MutationFunction<CreateCommentThreadMutation, CreateCommentThreadMutationVariables>;
|
||||
|
||||
/**
|
||||
* __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<CreateCommentThreadWithCommentMutation, CreateCommentThreadWithCommentMutationVariables>) {
|
||||
export function useCreateCommentThreadMutation(baseOptions?: Apollo.MutationHookOptions<CreateCommentThreadMutation, CreateCommentThreadMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<CreateCommentThreadWithCommentMutation, CreateCommentThreadWithCommentMutationVariables>(CreateCommentThreadWithCommentDocument, options);
|
||||
return Apollo.useMutation<CreateCommentThreadMutation, CreateCommentThreadMutationVariables>(CreateCommentThreadDocument, options);
|
||||
}
|
||||
export type CreateCommentThreadWithCommentMutationHookResult = ReturnType<typeof useCreateCommentThreadWithCommentMutation>;
|
||||
export type CreateCommentThreadWithCommentMutationResult = Apollo.MutationResult<CreateCommentThreadWithCommentMutation>;
|
||||
export type CreateCommentThreadWithCommentMutationOptions = Apollo.BaseMutationOptions<CreateCommentThreadWithCommentMutation, CreateCommentThreadWithCommentMutationVariables>;
|
||||
export type CreateCommentThreadMutationHookResult = ReturnType<typeof useCreateCommentThreadMutation>;
|
||||
export type CreateCommentThreadMutationResult = Apollo.MutationResult<CreateCommentThreadMutation>;
|
||||
export type CreateCommentThreadMutationOptions = Apollo.BaseMutationOptions<CreateCommentThreadMutation, CreateCommentThreadMutationVariables>;
|
||||
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<typeof useDeleteCommentThreadMutation>;
|
||||
export type DeleteCommentThreadMutationResult = Apollo.MutationResult<DeleteCommentThreadMutation>;
|
||||
export type DeleteCommentThreadMutationOptions = Apollo.BaseMutationOptions<DeleteCommentThreadMutation, DeleteCommentThreadMutationVariables>;
|
||||
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<UpdateCommentThreadTitleMutation, UpdateCommentThreadTitleMutationVariables>;
|
||||
|
||||
/**
|
||||
* __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<UpdateCommentThreadTitleMutation, UpdateCommentThreadTitleMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<UpdateCommentThreadTitleMutation, UpdateCommentThreadTitleMutationVariables>(UpdateCommentThreadTitleDocument, options);
|
||||
}
|
||||
export type UpdateCommentThreadTitleMutationHookResult = ReturnType<typeof useUpdateCommentThreadTitleMutation>;
|
||||
export type UpdateCommentThreadTitleMutationResult = Apollo.MutationResult<UpdateCommentThreadTitleMutation>;
|
||||
export type UpdateCommentThreadTitleMutationOptions = Apollo.BaseMutationOptions<UpdateCommentThreadTitleMutation, UpdateCommentThreadTitleMutationVariables>;
|
||||
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<UpdateCommentThreadBodyMutation, UpdateCommentThreadBodyMutationVariables>;
|
||||
|
||||
/**
|
||||
* __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<UpdateCommentThreadBodyMutation, UpdateCommentThreadBodyMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<UpdateCommentThreadBodyMutation, UpdateCommentThreadBodyMutationVariables>(UpdateCommentThreadBodyDocument, options);
|
||||
}
|
||||
export type UpdateCommentThreadBodyMutationHookResult = ReturnType<typeof useUpdateCommentThreadBodyMutation>;
|
||||
export type UpdateCommentThreadBodyMutationResult = Apollo.MutationResult<UpdateCommentThreadBodyMutation>;
|
||||
export type UpdateCommentThreadBodyMutationOptions = Apollo.BaseMutationOptions<UpdateCommentThreadBodyMutation, UpdateCommentThreadBodyMutationVariables>;
|
||||
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<typeof useGetCompaniesQuery>;
|
||||
export type GetCompaniesLazyQueryHookResult = ReturnType<typeof useGetCompaniesLazyQuery>;
|
||||
export type GetCompaniesQueryResult = Apollo.QueryResult<GetCompaniesQuery, GetCompaniesQueryVariables>;
|
||||
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<GetCompanyQuery, GetCompanyQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<GetCompanyQuery, GetCompanyQueryVariables>(GetCompanyDocument, options);
|
||||
}
|
||||
export function useGetCompanyLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetCompanyQuery, GetCompanyQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<GetCompanyQuery, GetCompanyQueryVariables>(GetCompanyDocument, options);
|
||||
}
|
||||
export type GetCompanyQueryHookResult = ReturnType<typeof useGetCompanyQuery>;
|
||||
export type GetCompanyLazyQueryHookResult = ReturnType<typeof useGetCompanyLazyQuery>;
|
||||
export type GetCompanyQueryResult = Apollo.QueryResult<GetCompanyQuery, GetCompanyQueryVariables>;
|
||||
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<typeof useGetPeopleQuery>;
|
||||
export type GetPeopleLazyQueryHookResult = ReturnType<typeof useGetPeopleLazyQuery>;
|
||||
export type GetPeopleQueryResult = Apollo.QueryResult<GetPeopleQuery, GetPeopleQueryVariables>;
|
||||
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<GetPersonQuery, GetPersonQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<GetPersonQuery, GetPersonQueryVariables>(GetPersonDocument, options);
|
||||
}
|
||||
export function useGetPersonLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetPersonQuery, GetPersonQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<GetPersonQuery, GetPersonQueryVariables>(GetPersonDocument, options);
|
||||
}
|
||||
export type GetPersonQueryHookResult = ReturnType<typeof useGetPersonQuery>;
|
||||
export type GetPersonLazyQueryHookResult = ReturnType<typeof useGetPersonLazyQuery>;
|
||||
export type GetPersonQueryResult = Apollo.QueryResult<GetPersonQuery, GetPersonQueryVariables>;
|
||||
export const UpdatePeopleDocument = gql`
|
||||
mutation UpdatePeople($id: String, $firstName: String, $lastName: String, $phone: String, $city: String, $companyId: String, $email: String, $createdAt: DateTime) {
|
||||
updateOnePerson(
|
||||
|
@ -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;
|
||||
`;
|
||||
|
||||
|
@ -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)`
|
||||
|
@ -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 (
|
||||
<StyledContainer>
|
||||
<StyledThreadItemListContainer>
|
||||
{commentThread.comments?.map((comment, index) => (
|
||||
<CommentThreadItem
|
||||
key={comment.id}
|
||||
comment={comment}
|
||||
actionBar={
|
||||
index === 0 ? (
|
||||
<CommentThreadActionBar commentThreadId={commentThread.id} />
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</StyledThreadItemListContainer>
|
||||
<CommentThreadRelationPicker commentThread={commentThread} />
|
||||
<AutosizeTextInput onValidate={handleSendComment} />
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
@ -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 (
|
||||
<StyledContainer>
|
||||
{displayCommentList && (
|
||||
<StyledThreadItemListContainer>
|
||||
{comments?.map((comment) => (
|
||||
<CommentThreadItem key={comment.id} comment={comment} />
|
||||
))}
|
||||
</StyledThreadItemListContainer>
|
||||
)}
|
||||
<AutosizeTextInput minRows={5} onValidate={handleNewComment} />
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
@ -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 (
|
||||
<RightDrawerPage>
|
||||
<RightDrawerTopBar title="Comments" />
|
||||
<RightDrawerBody>
|
||||
{commentThreads.map((commentThread) => (
|
||||
<CommentThread key={commentThread.id} commentThread={commentThread} />
|
||||
))}
|
||||
</RightDrawerBody>
|
||||
</RightDrawerPage>
|
||||
);
|
||||
}
|
@ -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<CommentThread, 'id' | 'body'>;
|
||||
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 (
|
||||
<BlockNoteStyledContainer>
|
||||
<BlockEditor editor={editor} />
|
||||
</BlockNoteStyledContainer>
|
||||
);
|
||||
}
|
@ -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<CommentThread, 'id'> & {
|
||||
comments: Array<CommentForDrawer>;
|
||||
};
|
||||
};
|
||||
|
||||
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 && (
|
||||
<StyledThreadItemListContainer>
|
||||
{commentThread?.comments?.map((comment, index) => (
|
||||
<CommentThreadItem key={comment.id} comment={comment} />
|
||||
))}
|
||||
</StyledThreadItemListContainer>
|
||||
)}
|
||||
|
||||
<StyledCommentActionBar>
|
||||
{currentUser && <AutosizeTextInput onValidate={handleSendComment} />}
|
||||
</StyledCommentActionBar>
|
||||
</>
|
||||
);
|
||||
}
|
@ -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<CommentThread, 'id'> & {
|
||||
commentThreadTargets: Array<
|
||||
Pick<CommentThreadTarget, 'id' | 'commentableId' | 'commentableType'>
|
||||
>;
|
||||
};
|
||||
};
|
||||
|
||||
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 (
|
||||
<StyledContainer>
|
||||
<StyledLabelContainer>
|
||||
<IconArrowUpRight size={16} color={theme.font.color.tertiary} />
|
||||
<StyledRelationLabel>Relations</StyledRelationLabel>
|
||||
</StyledLabelContainer>
|
||||
<StyledRelationContainer
|
||||
ref={refs.setReference}
|
||||
onClick={handleRelationContainerClick}
|
||||
@ -215,11 +191,12 @@ export function CommentThreadRelationPicker({ commentThread }: OwnProps) {
|
||||
entity.entityType === CommentableType.Company ? (
|
||||
<CompanyChip
|
||||
key={entity.id}
|
||||
id={entity.id}
|
||||
name={entity.name}
|
||||
picture={entity.avatarUrl}
|
||||
/>
|
||||
) : (
|
||||
<PersonChip key={entity.id} name={entity.name} />
|
||||
<PersonChip key={entity.id} name={entity.name} id={entity.id} />
|
||||
),
|
||||
)}
|
||||
</StyledRelationContainer>
|
@ -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: <IconNotes /> },
|
||||
// { label: 'Call', icon: <IconPhone /> },
|
||||
];
|
||||
|
||||
const handleSelect = (selectedOption: DropdownOptionType) => {
|
||||
// console.log(`You selected: ${selectedOption.label}`);
|
||||
};
|
||||
|
||||
return <DropdownButton options={options} onSelection={handleSelect} />;
|
||||
}
|
@ -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<typeof CommentThreadRelationPicker>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: getRenderWrapperForComponent(
|
||||
<StyledContainer>
|
||||
<CommentThreadRelationPicker commentThread={mockedCommentThreads[0]} />
|
||||
</StyledContainer>,
|
||||
<MemoryRouter>
|
||||
<StyledContainer>
|
||||
<CommentThreadRelationPicker commentThread={mockedCommentThreads[0]} />
|
||||
</StyledContainer>
|
||||
</MemoryRouter>,
|
||||
),
|
||||
};
|
@ -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;
|
@ -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<typeof CommentHeader> = {
|
||||
title: 'Modules/Comments/CommentHeader',
|
@ -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 (
|
||||
<StyledContainer>
|
||||
<StyledTopContainer>
|
||||
<StyledTopActionsContainer>
|
||||
<CommentThreadTypeDropdown />
|
||||
<CommentThreadActionBar commentThreadId={commentThread?.id ?? ''} />
|
||||
</StyledTopActionsContainer>
|
||||
<StyledEditableTitleInput
|
||||
placeholder="Note title (optional)"
|
||||
onChange={(event) => debounceUpdateTitle(event.target.value)}
|
||||
value={commentThread?.title ?? ''}
|
||||
/>
|
||||
<PropertyBox>
|
||||
<PropertyBoxItem
|
||||
icon={<IconArrowUpRight />}
|
||||
value={
|
||||
<CommentThreadRelationPicker
|
||||
commentThread={{
|
||||
id: commentThread.id,
|
||||
commentThreadTargets:
|
||||
commentThread.commentThreadTargets ?? [],
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Relations"
|
||||
/>
|
||||
</PropertyBox>
|
||||
</StyledTopContainer>
|
||||
<CommentThreadBodyEditor
|
||||
commentThread={commentThread}
|
||||
onChange={updateTitleFromBody}
|
||||
/>
|
||||
{showComment && (
|
||||
<CommentThreadComments
|
||||
commentThread={{
|
||||
id: commentThread.id,
|
||||
comments: commentThread.comments ?? [],
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</StyledContainer>
|
||||
);
|
||||
}
|
@ -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;
|
@ -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 (
|
||||
<RightDrawerPage>
|
||||
<RightDrawerTopBar title="Timeline" />
|
||||
<RightDrawerBody>
|
||||
{commentableEntityArray.map((commentableEntity) => (
|
||||
<Timeline
|
||||
key={commentableEntity.id}
|
||||
entity={{
|
||||
id: commentableEntity?.id ?? '',
|
||||
type: commentableEntity.type,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</RightDrawerBody>
|
||||
</RightDrawerPage>
|
||||
);
|
||||
}
|
@ -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 (
|
||||
<RightDrawerPage>
|
||||
<RightDrawerTopBar
|
||||
title="New note"
|
||||
onSave={() => {
|
||||
return;
|
||||
}}
|
||||
/>
|
||||
<RightDrawerBody>
|
||||
{commentThreadId && (
|
||||
<CommentThread
|
||||
commentThreadId={commentThreadId}
|
||||
showComment={false}
|
||||
/>
|
||||
)}
|
||||
</RightDrawerBody>
|
||||
</RightDrawerPage>
|
||||
);
|
||||
}
|
@ -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 (
|
||||
<RightDrawerPage>
|
||||
<RightDrawerTopBar title="New comment" />
|
||||
<RightDrawerTopBar title="" />
|
||||
<RightDrawerBody>
|
||||
<CommentThreadCreateMode />
|
||||
{commentThreadId && <CommentThread commentThreadId={commentThreadId} />}
|
||||
</RightDrawerBody>
|
||||
</RightDrawerPage>
|
||||
);
|
@ -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;
|
||||
`;
|
||||
|
288
front/src/modules/comments/components/timeline/Timeline.tsx
Normal file
288
front/src/modules/comments/components/timeline/Timeline.tsx
Normal file
@ -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 (
|
||||
<StyledTimelineEmptyContainer>
|
||||
<StyledEmptyTimelineTitle>No activity yet</StyledEmptyTimelineTitle>
|
||||
<StyledEmptyTimelineSubTitle>Create one:</StyledEmptyTimelineSubTitle>
|
||||
<TableActionBarButtonToggleComments
|
||||
onClick={() => openCreateCommandThread(entity)}
|
||||
/>
|
||||
</StyledTimelineEmptyContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledMainContainer>
|
||||
<StyledTopActionBar>
|
||||
<StyledTimelineItemContainer>
|
||||
<StyledIconContainer>
|
||||
<IconCirclePlus />
|
||||
</StyledIconContainer>
|
||||
|
||||
<TableActionBarButtonToggleComments
|
||||
onClick={() => openCreateCommandThread(entity)}
|
||||
/>
|
||||
</StyledTimelineItemContainer>
|
||||
</StyledTopActionBar>
|
||||
<StyledTimelineContainer>
|
||||
{commentThreads.map((commentThread) => {
|
||||
const beautifiedCreatedAt = beautifyPastDateRelativeToNow(
|
||||
commentThread.createdAt,
|
||||
);
|
||||
const exactCreatedAt = beautifyExactDate(commentThread.createdAt);
|
||||
const body = JSON.parse(commentThread.body ?? '{}')[0]?.content[0]
|
||||
?.text;
|
||||
|
||||
return (
|
||||
<React.Fragment key={commentThread.id}>
|
||||
<StyledTimelineItemContainer>
|
||||
<StyledIconContainer>
|
||||
<IconNotes />
|
||||
</StyledIconContainer>
|
||||
<StyledItemTitleContainer>
|
||||
<span>
|
||||
{commentThread.author.firstName}{' '}
|
||||
{commentThread.author.lastName}
|
||||
</span>
|
||||
created a note
|
||||
</StyledItemTitleContainer>
|
||||
<StyledItemTitleDate id={`id-${commentThread.id}`}>
|
||||
{beautifiedCreatedAt} ago
|
||||
</StyledItemTitleDate>
|
||||
<StyledTooltip
|
||||
anchorSelect={`#id-${commentThread.id}`}
|
||||
content={exactCreatedAt}
|
||||
clickable
|
||||
noArrow
|
||||
/>
|
||||
</StyledTimelineItemContainer>
|
||||
<StyledTimelineItemContainer>
|
||||
<StyledVerticalLineContainer>
|
||||
<StyledVerticalLine></StyledVerticalLine>
|
||||
</StyledVerticalLineContainer>
|
||||
<StyledCardContainer>
|
||||
<StyledCard
|
||||
onClick={() =>
|
||||
openCommentThreadRightDrawer(commentThread.id)
|
||||
}
|
||||
>
|
||||
<StyledCardTitle>
|
||||
{commentThread.title ? commentThread.title : '(No title)'}
|
||||
</StyledCardTitle>
|
||||
<StyledCardContent>
|
||||
{body ? body : '(No content)'}
|
||||
</StyledCardContent>
|
||||
</StyledCard>
|
||||
</StyledCardContainer>
|
||||
</StyledTimelineItemContainer>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</StyledTimelineContainer>
|
||||
</StyledMainContainer>
|
||||
);
|
||||
}
|
@ -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<CommentThread, 'id'> & {
|
||||
commentThreadTargets: Array<
|
||||
Pick<CommentThreadTarget, 'id' | 'commentableId'>
|
||||
>;
|
||||
};
|
||||
}) {
|
||||
const [addCommentThreadTargetOnCommentThread] =
|
||||
useAddCommentThreadTargetOnCommentThreadMutation({
|
||||
@ -39,6 +44,9 @@ export function useHandleCheckableCommentThreadTargetChange({
|
||||
newCheckedValue: boolean,
|
||||
entity: CommentableEntityForSelect,
|
||||
) {
|
||||
if (!commentThread) {
|
||||
return;
|
||||
}
|
||||
if (newCheckedValue) {
|
||||
addCommentThreadTargetOnCommentThread({
|
||||
variables: {
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
@ -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);
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -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);
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
@ -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);
|
||||
};
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -1,6 +0,0 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const createdCommentThreadIdState = atom<string | null>({
|
||||
key: 'comments/created-comment-thread-id',
|
||||
default: null,
|
||||
});
|
@ -0,0 +1,6 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const viewableCommentThreadIdState = atom<string | null>({
|
||||
key: 'comments/viewable-comment-thread-id',
|
||||
default: null,
|
||||
});
|
@ -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 (
|
||||
<StyledContainer data-testid="company-chip">
|
||||
<ContainerComponent data-testid="company-chip" to={`/companies/${id}`}>
|
||||
{picture && (
|
||||
<Avatar
|
||||
avatarUrl={picture?.toString()}
|
||||
@ -50,7 +64,7 @@ function CompanyChip({ name, picture }: CompanyChipPropsType) {
|
||||
/>
|
||||
)}
|
||||
<StyledName>{name}</StyledName>
|
||||
</StyledContainer>
|
||||
</ContainerComponent>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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<HTMLDivElement>) {
|
||||
@ -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={[
|
||||
<CellCommentChip
|
||||
count={company._commentCount ?? 0}
|
||||
count={company._commentThreadCount ?? 0}
|
||||
onClick={handleCommentClick}
|
||||
/>,
|
||||
]}
|
||||
|
@ -33,8 +33,8 @@ export const SmallName: Story = {
|
||||
render: getRenderWrapperForComponent(
|
||||
<TestCellContainer>
|
||||
<CompanyChip
|
||||
name="Instragram"
|
||||
picture="https://api.faviconkit.com/instagram.com/144"
|
||||
name="Airbnb"
|
||||
picture="https://api.faviconkit.com/airbnb.com/144"
|
||||
/>
|
||||
</TestCellContainer>,
|
||||
),
|
||||
|
@ -1,2 +1,3 @@
|
||||
export * from './select';
|
||||
export * from './show';
|
||||
export * from './update';
|
||||
|
@ -22,7 +22,7 @@ export const GET_COMPANIES = gql`
|
||||
createdAt
|
||||
address
|
||||
employees
|
||||
_commentCount
|
||||
_commentThreadCount
|
||||
accountOwner {
|
||||
id
|
||||
email
|
||||
|
26
front/src/modules/companies/services/show.ts
Normal file
26
front/src/modules/companies/services/show.ts
Normal file
@ -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 } });
|
||||
}
|
@ -16,4 +16,5 @@ export enum InternalHotkeysScope {
|
||||
PasswordLogin = 'password-login',
|
||||
AuthIndex = 'auth-index',
|
||||
CreateProfile = 'create-profile',
|
||||
ShowPage = 'show-page',
|
||||
}
|
||||
|
@ -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, 'id' | 'firstName' | 'lastName' | '_commentCount'>;
|
||||
person: Pick<Person, 'id' | 'firstName' | 'lastName' | '_commentThreadCount'>;
|
||||
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={
|
||||
<NoEditModeContainer>
|
||||
<PersonChip name={person.firstName + ' ' + person.lastName} />
|
||||
<PersonChip
|
||||
name={person.firstName + ' ' + person.lastName}
|
||||
id={person.id}
|
||||
/>
|
||||
<RightContainer>
|
||||
<CellCommentChip
|
||||
count={person._commentCount ?? 0}
|
||||
count={person._commentThreadCount ?? 0}
|
||||
onClick={handleCommentClick}
|
||||
/>
|
||||
</RightContainer>
|
||||
|
@ -30,6 +30,7 @@ export function PeopleCompanyCell({ people }: OwnProps) {
|
||||
}
|
||||
nonEditModeContent={
|
||||
<CompanyChip
|
||||
id={people.company?.id ?? ''}
|
||||
name={people.company?.name ?? ''}
|
||||
picture={getLogoUrlFromDomainName(people.company?.domainName)}
|
||||
/>
|
||||
|
@ -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 (
|
||||
<StyledContainer data-testid="person-chip">
|
||||
<ContainerComponent data-testid="person-chip" to={`/person/${id}`}>
|
||||
<img
|
||||
data-testid="person-chip-image"
|
||||
src={picture ? picture.toString() : PersonPlaceholder.toString()}
|
||||
alt="person"
|
||||
/>
|
||||
<StyledName>{name}</StyledName>
|
||||
</StyledContainer>
|
||||
</ContainerComponent>
|
||||
);
|
||||
}
|
||||
|
@ -1,2 +1,3 @@
|
||||
export * from './select';
|
||||
export * from './show';
|
||||
export * from './update';
|
||||
|
@ -24,7 +24,7 @@ export const GET_PEOPLE = gql`
|
||||
firstName
|
||||
lastName
|
||||
createdAt
|
||||
_commentCount
|
||||
_commentThreadCount
|
||||
company {
|
||||
id
|
||||
name
|
||||
|
19
front/src/modules/people/services/show.ts
Normal file
19
front/src/modules/people/services/show.ts
Normal file
@ -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 } });
|
||||
}
|
94
front/src/modules/ui/components/buttons/DropdownButton.tsx
Normal file
94
front/src/modules/ui/components/buttons/DropdownButton.tsx
Normal file
@ -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<ButtonProps>`
|
||||
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<HTMLButtonElement>) => {
|
||||
event.preventDefault();
|
||||
onSelection(option);
|
||||
setSelectedOption(option);
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownContainer>
|
||||
<StyledButton onClick={() => setIsOpen(!isOpen)} {...buttonProps}>
|
||||
{selectedOption.icon}
|
||||
{selectedOption.label}
|
||||
{options.length > 1 && <IconChevronDown />}
|
||||
</StyledButton>
|
||||
{isOpen && (
|
||||
<DropdownMenu>
|
||||
{options
|
||||
.filter((option) => option.label !== selectedOption.label)
|
||||
.map((option, index) => (
|
||||
<StyledButton key={index} onClick={handleSelect(option)}>
|
||||
{option.icon}
|
||||
{option.label}
|
||||
</StyledButton>
|
||||
))}
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</DropdownContainer>
|
||||
);
|
||||
}
|
@ -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<HTMLDivElement>) => 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={
|
||||
<NoEditModeContainer>
|
||||
<ChipComponent name={inputValue} picture={picture} />
|
||||
<ChipComponent id={id} name={inputValue} picture={picture} />
|
||||
<RightContainer>
|
||||
{rightEndContents &&
|
||||
rightEndContents.length > 0 &&
|
||||
|
28
front/src/modules/ui/components/editor/BlockEditor.tsx
Normal file
28
front/src/modules/ui/components/editor/BlockEditor.tsx
Normal file
@ -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 (
|
||||
<StyledEditor>
|
||||
<BlockNoteView editor={editor} />
|
||||
</StyledEditor>
|
||||
);
|
||||
}
|
@ -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 {
|
||||
|
@ -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({
|
||||
<>
|
||||
<StyledContainer>
|
||||
<StyledTextArea
|
||||
placeholder={placeholder || 'Write something...'}
|
||||
placeholder={placeholder || 'Write a comment'}
|
||||
maxRows={MAX_ROWS}
|
||||
minRows={computedMinRows}
|
||||
onChange={handleInputChange}
|
||||
|
21
front/src/modules/ui/components/property-box/PropertyBox.tsx
Normal file
21
front/src/modules/ui/components/property-box/PropertyBox.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledPropertyBoxContainer = styled.div`
|
||||
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: ${({ 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 <StyledPropertyBoxContainer>{children}</StyledPropertyBoxContainer>;
|
||||
}
|
@ -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 (
|
||||
<StyledPropertyBoxItem>
|
||||
<StyledLabelAndIconContainer>
|
||||
<StyledIconContainer>{icon}</StyledIconContainer>
|
||||
{label}
|
||||
</StyledLabelAndIconContainer>
|
||||
<StyledValueContainer>{value}</StyledValueContainer>
|
||||
</StyledPropertyBoxItem>
|
||||
);
|
||||
}
|
@ -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;
|
||||
`;
|
||||
|
||||
|
@ -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)};
|
||||
`;
|
||||
|
@ -32,7 +32,7 @@ const StyledButton = styled.div<StyledButtonProps>`
|
||||
`;
|
||||
|
||||
const StyledButtonLabel = styled.div`
|
||||
font-weight: 500;
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
margin-left: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
|
@ -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 (
|
||||
<EntityTableActionBarButton
|
||||
label="Comment"
|
||||
icon={<IconComment size={16} />}
|
||||
label="Notes"
|
||||
icon={<IconNotes size={16} />}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
|
@ -179,7 +179,7 @@ function DropdownButton({
|
||||
};
|
||||
|
||||
const dropdownRef = useRef(null);
|
||||
useOutsideAlerter(dropdownRef, onOutsideClick);
|
||||
useOutsideAlerter({ ref: dropdownRef, callback: onOutsideClick });
|
||||
|
||||
return (
|
||||
<StyledDropdownButtonContainer>
|
||||
|
@ -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) => {
|
||||
|
@ -45,7 +45,7 @@ const StyledDelete = styled.div`
|
||||
`;
|
||||
|
||||
const StyledLabelKey = styled.div`
|
||||
font-weight: 500;
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
`;
|
||||
|
||||
function SortOrFilterChip({
|
||||
|
@ -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;
|
||||
`;
|
||||
|
||||
|
@ -7,7 +7,7 @@ const onOutsideClick = jest.fn();
|
||||
|
||||
function TestComponent() {
|
||||
const buttonRef = useRef(null);
|
||||
useOutsideAlerter(buttonRef, onOutsideClick);
|
||||
useOutsideAlerter({ ref: buttonRef, callback: onOutsideClick });
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -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<HTMLInputElement>,
|
||||
callback: CallbackType,
|
||||
) {
|
||||
type OwnProps = {
|
||||
ref: React.RefObject<HTMLInputElement>;
|
||||
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]);
|
||||
}
|
||||
|
@ -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';
|
||||
|
@ -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<LeftContainerProps>`
|
||||
const StyledLeftContainer = styled.div<LeftContainerProps>`
|
||||
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 (
|
||||
<MainContainer topMargin={topMargin ?? 0}>
|
||||
<LeftContainer isRightDrawerOpen={isRightDrawerOpen}>
|
||||
<StyledMainContainer topMargin={topMargin ?? 0}>
|
||||
<StyledLeftContainer isRightDrawerOpen={isRightDrawerOpen}>
|
||||
<Panel>{children}</Panel>
|
||||
</LeftContainer>
|
||||
</StyledLeftContainer>
|
||||
<RightDrawer />
|
||||
</MainContainer>
|
||||
</StyledMainContainer>
|
||||
);
|
||||
}
|
||||
|
@ -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)};
|
||||
|
@ -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)};
|
||||
`;
|
||||
|
||||
|
@ -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 (
|
||||
<StyledRightDrawer>
|
||||
<Panel>
|
||||
<RightDrawerRouter />
|
||||
</Panel>
|
||||
</StyledRightDrawer>
|
||||
<>
|
||||
<StyledContainer>
|
||||
<StyledRightDrawer ref={rightDrawerRef}>
|
||||
<RightDrawerRouter />
|
||||
</StyledRightDrawer>
|
||||
</StyledContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
`;
|
||||
|
@ -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' ? (
|
||||
<RightDrawerComments />
|
||||
) : rightDrawerPage === 'create-comment-thread' ? (
|
||||
<RightDrawerCreateCommentThread />
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
switch (rightDrawerPage) {
|
||||
case RightDrawerPages.Timeline:
|
||||
return <RightDrawerTimeline />;
|
||||
case RightDrawerPages.CreateCommentThread:
|
||||
return <RightDrawerCreateCommentThread />;
|
||||
case RightDrawerPages.EditCommentThread:
|
||||
return <RightDrawerEditCommentThread />;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
|
@ -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 (
|
||||
<StyledRightDrawerTopBar>
|
||||
<StyledTopBarTitle>{title}</StyledTopBarTitle>
|
||||
<RightDrawerTopBarCloseButton />
|
||||
<StyledTopBarTitle>{title}</StyledTopBarTitle>
|
||||
{onSave && <Button title="Save" onClick={handleOnClick} />}
|
||||
</StyledRightDrawerTopBar>
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { IconPlus } from '@/ui/icons/index';
|
||||
import { IconChevronsRight } from '@/ui/icons/index';
|
||||
|
||||
import { isRightDrawerOpenState } from '../states/isRightDrawerOpenState';
|
||||
|
||||
@ -24,7 +24,6 @@ const StyledButton = styled.button`
|
||||
}
|
||||
svg {
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
`;
|
||||
|
||||
@ -37,7 +36,7 @@ export function RightDrawerTopBarCloseButton() {
|
||||
|
||||
return (
|
||||
<StyledButton onClick={handleButtonClick}>
|
||||
<IconPlus size={16} />
|
||||
<IconChevronsRight size={16} />
|
||||
</StyledButton>
|
||||
);
|
||||
}
|
||||
|
@ -2,13 +2,13 @@ import { useRecoilState } from 'recoil';
|
||||
|
||||
import { isRightDrawerOpenState } from '../states/isRightDrawerOpenState';
|
||||
import { rightDrawerPageState } from '../states/rightDrawerPageState';
|
||||
import { RightDrawerPage } from '../types/RightDrawerPage';
|
||||
import { RightDrawerPages } from '../types/RightDrawerPages';
|
||||
|
||||
export function useOpenRightDrawer() {
|
||||
const [, setIsRightDrawerOpen] = useRecoilState(isRightDrawerOpenState);
|
||||
const [, setRightDrawerPage] = useRecoilState(rightDrawerPageState);
|
||||
|
||||
return function openRightDrawer(rightDrawerPage: RightDrawerPage) {
|
||||
return function openRightDrawer(rightDrawerPage: RightDrawerPages) {
|
||||
setRightDrawerPage(rightDrawerPage);
|
||||
setIsRightDrawerOpen(true);
|
||||
};
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
import { RightDrawerPage } from '../types/RightDrawerPage';
|
||||
import { RightDrawerPages } from '../types/RightDrawerPages';
|
||||
|
||||
export const rightDrawerPageState = atom<RightDrawerPage | null>({
|
||||
export const rightDrawerPageState = atom<RightDrawerPages | null>({
|
||||
key: 'ui/layout/right-drawer-page',
|
||||
default: null,
|
||||
});
|
||||
|
@ -1 +0,0 @@
|
||||
export type RightDrawerPage = 'comments' | 'create-comment-thread';
|
@ -0,0 +1,5 @@
|
||||
export enum RightDrawerPages {
|
||||
Timeline = 'timeline',
|
||||
CreateCommentThread = 'create-comment-thread',
|
||||
EditCommentThread = 'edit-comment-thread',
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
|
||||
import { Avatar } from '@/users/components/Avatar';
|
||||
import {
|
||||
beautifyExactDate,
|
||||
beautifyPastDateRelativeToNow,
|
||||
} from '@/utils/datetime/date-utils';
|
||||
|
||||
const StyledShowPageSummaryCard = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(3)};
|
||||
justify-content: center;
|
||||
padding: ${({ theme }) => theme.spacing(6)} ${({ theme }) => theme.spacing(3)}
|
||||
${({ theme }) => theme.spacing(3)} ${({ theme }) => theme.spacing(3)};
|
||||
`;
|
||||
|
||||
const StyledInfoContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const StyledDate = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const StyledTitle = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
font-size: ${({ theme }) => theme.font.size.xl};
|
||||
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
||||
`;
|
||||
|
||||
const StyledTooltip = styled(Tooltip)`
|
||||
background-color: ${({ theme }) => theme.background.primary};
|
||||
box-shadow: ${({ theme }) => theme.boxShadow.light};
|
||||
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
export function ShowPageSummaryCard({
|
||||
logoOrAvatar,
|
||||
title,
|
||||
date,
|
||||
}: {
|
||||
logoOrAvatar?: string;
|
||||
title: string;
|
||||
date: string;
|
||||
}) {
|
||||
const beautifiedCreatedAt =
|
||||
date !== '' ? beautifyPastDateRelativeToNow(date) : '';
|
||||
const exactCreatedAt = date !== '' ? beautifyExactDate(date) : '';
|
||||
const theme = useTheme();
|
||||
const dateElementId = `date-id-${uuidV4()}`;
|
||||
|
||||
return (
|
||||
<StyledShowPageSummaryCard>
|
||||
<Avatar
|
||||
avatarUrl={logoOrAvatar}
|
||||
size={theme.icon.size.xl}
|
||||
placeholder={title}
|
||||
/>
|
||||
<StyledInfoContainer>
|
||||
<StyledTitle>{title}</StyledTitle>
|
||||
<StyledDate id={dateElementId}>
|
||||
Added {beautifiedCreatedAt} ago
|
||||
</StyledDate>
|
||||
<StyledTooltip
|
||||
anchorSelect={`#${dateElementId}`}
|
||||
content={exactCreatedAt}
|
||||
clickable
|
||||
noArrow
|
||||
place="right"
|
||||
/>
|
||||
</StyledInfoContainer>
|
||||
</StyledShowPageSummaryCard>
|
||||
);
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
export const ShowPageLeftContainer = styled.div`
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
border-radius: 8px;
|
||||
border-right: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
padding: 0px ${({ theme }) => theme.spacing(3)};
|
||||
width: 320px;
|
||||
`;
|
@ -0,0 +1,9 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
export const ShowPageRightContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1 0 0;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
`;
|
@ -3,6 +3,7 @@ export const icon = {
|
||||
sm: 14,
|
||||
md: 16,
|
||||
lg: 20,
|
||||
xl: 40,
|
||||
},
|
||||
stroke: {
|
||||
sm: 1.6,
|
||||
|
@ -18,7 +18,7 @@ const common = {
|
||||
horizontalCellMargin: '8px',
|
||||
checkboxColumnWidth: '32px',
|
||||
},
|
||||
rightDrawerWidth: '300px',
|
||||
rightDrawerWidth: '500px',
|
||||
clickableElementBackgroundTransition: 'background 0.1s ease',
|
||||
lastLayerZIndex: 2147483647,
|
||||
};
|
||||
@ -32,6 +32,7 @@ export const lightTheme = {
|
||||
selectedCardHover: color.blue20,
|
||||
selectedCard: color.blue10,
|
||||
font: fontLight,
|
||||
name: 'light',
|
||||
},
|
||||
};
|
||||
export type ThemeType = typeof lightTheme;
|
||||
@ -45,6 +46,7 @@ export const darkTheme: ThemeType = {
|
||||
selectedCardHover: color.blue70,
|
||||
selectedCard: color.blue80,
|
||||
font: fontDark,
|
||||
name: 'dark',
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -17,8 +17,10 @@ export const StyledAvatar = styled.div<Omit<OwnProps, 'placeholder'>>`
|
||||
!isNonEmptyString(props.avatarUrl)
|
||||
? props.theme.background.tertiary
|
||||
: 'none'};
|
||||
background-image: url(${(props) =>
|
||||
isNonEmptyString(props.avatarUrl) ? props.avatarUrl : 'none'});
|
||||
${(props) =>
|
||||
isNonEmptyString(props.avatarUrl)
|
||||
? `background-image: url(${props.avatarUrl});`
|
||||
: ''}
|
||||
background-size: cover;
|
||||
border-radius: ${(props) => (props.type === 'rounded' ? '50%' : '2px')};
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
|
@ -1,10 +1,8 @@
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { isMockModeState } from '@/auth/states/isMockModeState';
|
||||
import { GET_COMPANIES } from '@/companies/services';
|
||||
import { useHotkeysScopeOnMountOnly } from '@/hotkeys/hooks/useHotkeysScopeOnMountOnly';
|
||||
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
|
||||
@ -28,17 +26,10 @@ const StyledTableContainer = styled.div`
|
||||
`;
|
||||
|
||||
export function Companies() {
|
||||
const [isMockMode] = useRecoilState(isMockModeState);
|
||||
|
||||
const hotkeysEnabled = !isMockMode;
|
||||
|
||||
useHotkeysScopeOnMountOnly(
|
||||
{
|
||||
scope: InternalHotkeysScope.Table,
|
||||
customScopes: { 'command-menu': true, goto: true },
|
||||
},
|
||||
hotkeysEnabled,
|
||||
);
|
||||
useHotkeysScopeOnMountOnly({
|
||||
scope: InternalHotkeysScope.Table,
|
||||
customScopes: { 'command-menu': true, goto: true },
|
||||
});
|
||||
|
||||
const [insertCompany] = useInsertCompanyMutation();
|
||||
|
||||
|
75
front/src/pages/companies/CompanyShow.tsx
Normal file
75
front/src/pages/companies/CompanyShow.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useTheme } from '@emotion/react';
|
||||
|
||||
import { Timeline } from '@/comments/components/timeline/Timeline';
|
||||
import { useCompanyQuery } from '@/companies/services';
|
||||
import { useHotkeysScopeOnMountOnly } from '@/hotkeys/hooks/useHotkeysScopeOnMountOnly';
|
||||
import { InternalHotkeysScope } from '@/hotkeys/types/internal/InternalHotkeysScope';
|
||||
import { RawLink } from '@/ui/components/links/RawLink';
|
||||
import { PropertyBox } from '@/ui/components/property-box/PropertyBox';
|
||||
import { PropertyBoxItem } from '@/ui/components/property-box/PropertyBoxItem';
|
||||
import { IconBuildingSkyscraper, IconLink, IconMap } from '@/ui/icons/index';
|
||||
import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer';
|
||||
import { ShowPageLeftContainer } from '@/ui/layout/show-page/containers/ShowPageLeftContainer';
|
||||
import { ShowPageRightContainer } from '@/ui/layout/show-page/containers/ShowPageRightContainer';
|
||||
import { ShowPageSummaryCard } from '@/ui/layout/show-page/ShowPageSummaryCard';
|
||||
import { getLogoUrlFromDomainName } from '@/utils/utils';
|
||||
import { CommentableType } from '~/generated/graphql';
|
||||
|
||||
export function CompanyShow() {
|
||||
const companyId = useParams().companyId ?? '';
|
||||
|
||||
useHotkeysScopeOnMountOnly({
|
||||
scope: InternalHotkeysScope.ShowPage,
|
||||
customScopes: { 'command-menu': true, goto: true },
|
||||
});
|
||||
|
||||
const { data } = useCompanyQuery(companyId);
|
||||
const company = data?.findUniqueCompany;
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<WithTopBarContainer
|
||||
title={company?.name ?? ''}
|
||||
icon={<IconBuildingSkyscraper size={theme.icon.size.md} />}
|
||||
>
|
||||
<>
|
||||
<ShowPageLeftContainer>
|
||||
<ShowPageSummaryCard
|
||||
logoOrAvatar={getLogoUrlFromDomainName(company?.domainName ?? '')}
|
||||
title={company?.name ?? 'No name'}
|
||||
date={company?.createdAt ?? ''}
|
||||
/>
|
||||
<PropertyBox extraPadding={true}>
|
||||
<>
|
||||
<PropertyBoxItem
|
||||
icon={<IconLink />}
|
||||
value={
|
||||
<RawLink
|
||||
href={
|
||||
company?.domainName
|
||||
? 'https://' + company?.domainName
|
||||
: ''
|
||||
}
|
||||
>
|
||||
{company?.domainName}
|
||||
</RawLink>
|
||||
}
|
||||
/>
|
||||
<PropertyBoxItem
|
||||
icon={<IconMap />}
|
||||
value={company?.address ? company?.address : 'No address'}
|
||||
/>
|
||||
</>
|
||||
</PropertyBox>
|
||||
</ShowPageLeftContainer>
|
||||
<ShowPageRightContainer>
|
||||
<Timeline
|
||||
entity={{ id: company?.id ?? '', type: CommentableType.Company }}
|
||||
/>
|
||||
</ShowPageRightContainer>
|
||||
</>
|
||||
</WithTopBarContainer>
|
||||
);
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
import { expect } from '@storybook/jest';
|
||||
import type { Meta } from '@storybook/react';
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { getRenderWrapperForPage } from '~/testing/renderWrappers';
|
||||
|
||||
import { Companies } from '../Companies';
|
||||
|
||||
import { Story } from './Companies.stories';
|
||||
|
||||
const meta: Meta<typeof Companies> = {
|
||||
title: 'Pages/Companies/Comments',
|
||||
component: Companies,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
export const OpenCommentsSection: Story = {
|
||||
render: getRenderWrapperForPage(<Companies />, '/companies'),
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const firstRow = await canvas.findByTestId('row-id-1');
|
||||
|
||||
expect(firstRow).toBeDefined();
|
||||
|
||||
const commentsChip = await within(firstRow).findByTestId('comment-chip');
|
||||
expect(commentsChip).toBeDefined();
|
||||
|
||||
userEvent.click(commentsChip);
|
||||
const commentSection = await canvas.findByText('Comments');
|
||||
expect(commentSection).toBeDefined();
|
||||
},
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
};
|
80
front/src/pages/companies/__stories__/Company.stories.tsx
Normal file
80
front/src/pages/companies/__stories__/Company.stories.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { within } from '@storybook/testing-library';
|
||||
import { graphql } from 'msw';
|
||||
|
||||
import {
|
||||
GET_COMMENT_THREAD,
|
||||
GET_COMMENT_THREADS_BY_TARGETS,
|
||||
} from '@/comments/services';
|
||||
import { CREATE_COMMENT_THREAD_WITH_COMMENT } from '@/comments/services/create';
|
||||
import { GET_COMPANY } from '@/companies/services';
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { mockedCommentThreads } from '~/testing/mock-data/comment-threads';
|
||||
import { mockedCompaniesData } from '~/testing/mock-data/companies';
|
||||
import { getRenderWrapperForPage } from '~/testing/renderWrappers';
|
||||
|
||||
import { CompanyShow } from '../CompanyShow';
|
||||
|
||||
const meta: Meta<typeof CompanyShow> = {
|
||||
title: 'Pages/Companies/Company',
|
||||
component: CompanyShow,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
export type Story = StoryObj<typeof CompanyShow>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: getRenderWrapperForPage(
|
||||
<CompanyShow />,
|
||||
'/companies/89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
||||
),
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const notesButton = await canvas.findByText('Notes');
|
||||
await notesButton.click();
|
||||
},
|
||||
parameters: {
|
||||
msw: [
|
||||
...graphqlMocks,
|
||||
graphql.mutation(
|
||||
getOperationName(CREATE_COMMENT_THREAD_WITH_COMMENT) ?? '',
|
||||
(req, res, ctx) => {
|
||||
return res(
|
||||
ctx.data({
|
||||
createOneCommentThread: mockedCommentThreads[0],
|
||||
}),
|
||||
);
|
||||
},
|
||||
),
|
||||
graphql.query(
|
||||
getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '',
|
||||
(req, res, ctx) => {
|
||||
return res(
|
||||
ctx.data({
|
||||
findManyCommentThreads: mockedCommentThreads,
|
||||
}),
|
||||
);
|
||||
},
|
||||
),
|
||||
graphql.query(
|
||||
getOperationName(GET_COMMENT_THREAD) ?? '',
|
||||
(req, res, ctx) => {
|
||||
return res(
|
||||
ctx.data({
|
||||
findManyCommentThreads: mockedCommentThreads[0],
|
||||
}),
|
||||
);
|
||||
},
|
||||
),
|
||||
graphql.query(getOperationName(GET_COMPANY) ?? '', (req, res, ctx) => {
|
||||
return res(
|
||||
ctx.data({
|
||||
findUniqueCompany: mockedCompaniesData[0],
|
||||
}),
|
||||
);
|
||||
}),
|
||||
],
|
||||
},
|
||||
};
|
51
front/src/pages/people/PersonShow.tsx
Normal file
51
front/src/pages/people/PersonShow.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useTheme } from '@emotion/react';
|
||||
|
||||
import { Timeline } from '@/comments/components/timeline/Timeline';
|
||||
import { usePersonQuery } from '@/people/services';
|
||||
import { PropertyBox } from '@/ui/components/property-box/PropertyBox';
|
||||
import { PropertyBoxItem } from '@/ui/components/property-box/PropertyBoxItem';
|
||||
import { IconLink, IconUser } from '@/ui/icons/index';
|
||||
import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer';
|
||||
import { ShowPageLeftContainer } from '@/ui/layout/show-page/containers/ShowPageLeftContainer';
|
||||
import { ShowPageRightContainer } from '@/ui/layout/show-page/containers/ShowPageRightContainer';
|
||||
import { ShowPageSummaryCard } from '@/ui/layout/show-page/ShowPageSummaryCard';
|
||||
import { CommentableType } from '~/generated/graphql';
|
||||
|
||||
export function PersonShow() {
|
||||
const personId = useParams().personId ?? '';
|
||||
|
||||
const { data } = usePersonQuery(personId);
|
||||
const person = data?.findUniquePerson;
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<WithTopBarContainer
|
||||
title={person?.firstName ?? ''}
|
||||
icon={<IconUser size={theme.icon.size.md} />}
|
||||
>
|
||||
<>
|
||||
<ShowPageLeftContainer>
|
||||
<ShowPageSummaryCard
|
||||
title={person?.displayName ?? 'No name'}
|
||||
date={person?.createdAt ?? ''}
|
||||
/>
|
||||
<PropertyBox extraPadding={true}>
|
||||
<>
|
||||
<PropertyBoxItem
|
||||
icon={<IconLink />}
|
||||
value={person?.firstName ?? 'No First name'}
|
||||
/>
|
||||
</>
|
||||
</PropertyBox>
|
||||
</ShowPageLeftContainer>
|
||||
<ShowPageRightContainer>
|
||||
<Timeline
|
||||
entity={{ id: person?.id ?? '', type: CommentableType.Person }}
|
||||
/>
|
||||
</ShowPageRightContainer>
|
||||
</>
|
||||
</WithTopBarContainer>
|
||||
);
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
import { expect } from '@storybook/jest';
|
||||
import type { Meta } from '@storybook/react';
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { getRenderWrapperForPage } from '~/testing/renderWrappers';
|
||||
|
||||
import { People } from '../People';
|
||||
|
||||
import { Story } from './People.stories';
|
||||
|
||||
const meta: Meta<typeof People> = {
|
||||
title: 'Pages/People/Comments',
|
||||
component: People,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
export const OpenCommentsSection: Story = {
|
||||
render: getRenderWrapperForPage(<People />, '/people'),
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
|
||||
const firstRow = await canvas.findByTestId('row-id-1');
|
||||
|
||||
expect(firstRow).toBeDefined();
|
||||
|
||||
const commentsChip = await within(firstRow).findByTestId('comment-chip');
|
||||
expect(commentsChip).toBeDefined();
|
||||
|
||||
userEvent.click(commentsChip);
|
||||
const commentSection = await canvas.findByText('Comments');
|
||||
expect(commentSection).toBeDefined();
|
||||
},
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
};
|
25
front/src/pages/people/__stories__/Person.stories.tsx
Normal file
25
front/src/pages/people/__stories__/Person.stories.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
||||
import { getRenderWrapperForPage } from '~/testing/renderWrappers';
|
||||
|
||||
import { PersonShow } from '../PersonShow';
|
||||
|
||||
const meta: Meta<typeof PersonShow> = {
|
||||
title: 'Pages/People/Person',
|
||||
component: PersonShow,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
export type Story = StoryObj<typeof PersonShow>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: getRenderWrapperForPage(
|
||||
<PersonShow />,
|
||||
'/companies/89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
||||
),
|
||||
parameters: {
|
||||
msw: graphqlMocks,
|
||||
},
|
||||
};
|
@ -1,4 +1,5 @@
|
||||
import {
|
||||
Comment,
|
||||
CommentableType,
|
||||
CommentThread,
|
||||
CommentThreadTarget,
|
||||
@ -6,8 +7,21 @@ import {
|
||||
|
||||
type MockedCommentThread = Pick<
|
||||
CommentThread,
|
||||
'id' | 'createdAt' | 'updatedAt' | '__typename'
|
||||
| 'id'
|
||||
| 'createdAt'
|
||||
| 'updatedAt'
|
||||
| '__typename'
|
||||
| 'body'
|
||||
| 'title'
|
||||
| 'authorId'
|
||||
> & {
|
||||
author: {
|
||||
__typename?: 'User' | undefined;
|
||||
id: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
};
|
||||
comments: Array<Pick<Comment, 'body'>>;
|
||||
commentThreadTargets: Array<
|
||||
Pick<
|
||||
CommentThreadTarget,
|
||||
@ -27,6 +41,15 @@ export const mockedCommentThreads: Array<MockedCommentThread> = [
|
||||
id: '89bb825c-171e-4bcc-9cf7-43448d6fb230',
|
||||
createdAt: '2023-04-26T10:12:42.33625+00:00',
|
||||
updatedAt: '2023-04-26T10:23:42.33625+00:00',
|
||||
title: 'My very first note',
|
||||
body: null,
|
||||
author: {
|
||||
id: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
|
||||
firstName: 'Charles',
|
||||
lastName: 'Test',
|
||||
},
|
||||
authorId: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
|
||||
comments: [],
|
||||
commentThreadTargets: [
|
||||
{
|
||||
id: '89bb825c-171e-4bcc-9cf7-43448d6fb300',
|
||||
@ -63,6 +86,15 @@ export const mockedCommentThreads: Array<MockedCommentThread> = [
|
||||
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
title: 'Another note',
|
||||
body: null,
|
||||
author: {
|
||||
id: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
|
||||
firstName: 'Charles',
|
||||
lastName: 'Test',
|
||||
},
|
||||
authorId: '374fe3a5-df1e-4119-afe0-2a62a2ba481e',
|
||||
comments: [],
|
||||
commentThreadTargets: [
|
||||
{
|
||||
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
|
||||
|
@ -9,7 +9,7 @@ type MockedCompany = Pick<
|
||||
| 'createdAt'
|
||||
| 'address'
|
||||
| 'employees'
|
||||
| '_commentCount'
|
||||
| '_commentThreadCount'
|
||||
> & {
|
||||
accountOwner: Pick<
|
||||
User,
|
||||
@ -25,7 +25,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
||||
createdAt: '2023-04-26T10:08:54.724515+00:00',
|
||||
address: '17 rue de clignancourt',
|
||||
employees: 12,
|
||||
_commentCount: 1,
|
||||
_commentThreadCount: 1,
|
||||
accountOwner: {
|
||||
email: 'charles@test.com',
|
||||
displayName: 'Charles Test',
|
||||
@ -43,7 +43,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
||||
createdAt: '2023-04-26T10:12:42.33625+00:00',
|
||||
address: '',
|
||||
employees: 1,
|
||||
_commentCount: 1,
|
||||
_commentThreadCount: 1,
|
||||
accountOwner: null,
|
||||
__typename: 'Company',
|
||||
},
|
||||
@ -54,7 +54,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
||||
createdAt: '2023-04-26T10:10:32.530184+00:00',
|
||||
address: '',
|
||||
employees: 1,
|
||||
_commentCount: 1,
|
||||
_commentThreadCount: 1,
|
||||
accountOwner: null,
|
||||
__typename: 'Company',
|
||||
},
|
||||
@ -65,7 +65,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
||||
createdAt: '2023-03-21T06:30:25.39474+00:00',
|
||||
address: '',
|
||||
employees: 10,
|
||||
_commentCount: 0,
|
||||
_commentThreadCount: 0,
|
||||
accountOwner: null,
|
||||
__typename: 'Company',
|
||||
},
|
||||
@ -76,7 +76,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
||||
createdAt: '2023-04-26T10:13:29.712485+00:00',
|
||||
address: '10 rue de la Paix',
|
||||
employees: 1,
|
||||
_commentCount: 2,
|
||||
_commentThreadCount: 2,
|
||||
accountOwner: null,
|
||||
__typename: 'Company',
|
||||
},
|
||||
@ -87,7 +87,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
||||
createdAt: '2023-04-26T10:09:25.656555+00:00',
|
||||
address: '',
|
||||
employees: 1,
|
||||
_commentCount: 13,
|
||||
_commentThreadCount: 13,
|
||||
accountOwner: null,
|
||||
__typename: 'Company',
|
||||
},
|
||||
@ -98,7 +98,7 @@ export const mockedCompaniesData: Array<MockedCompany> = [
|
||||
createdAt: '2023-04-26T10:09:25.656555+00:00',
|
||||
address: '',
|
||||
employees: 1,
|
||||
_commentCount: 1,
|
||||
_commentThreadCount: 1,
|
||||
accountOwner: null,
|
||||
__typename: 'Company',
|
||||
},
|
||||
|
@ -9,7 +9,7 @@ type MockedPerson = Pick<
|
||||
| '__typename'
|
||||
| 'phone'
|
||||
| 'city'
|
||||
| '_commentCount'
|
||||
| '_commentThreadCount'
|
||||
| 'createdAt'
|
||||
> & {
|
||||
company: Pick<Company, 'id' | 'name' | 'domainName' | '__typename'>;
|
||||
@ -29,7 +29,7 @@ export const mockedPeopleData: Array<MockedPerson> = [
|
||||
__typename: 'Company',
|
||||
},
|
||||
phone: '06 12 34 56 78',
|
||||
_commentCount: 1,
|
||||
_commentThreadCount: 1,
|
||||
createdAt: '2023-04-20T13:20:09.158312+00:00',
|
||||
|
||||
city: 'Paris',
|
||||
@ -47,7 +47,7 @@ export const mockedPeopleData: Array<MockedPerson> = [
|
||||
__typename: 'Company',
|
||||
},
|
||||
phone: '06 12 34 56 78',
|
||||
_commentCount: 1,
|
||||
_commentThreadCount: 1,
|
||||
createdAt: '2023-04-20T13:20:09.158312+00:00',
|
||||
|
||||
city: 'Paris',
|
||||
@ -65,7 +65,7 @@ export const mockedPeopleData: Array<MockedPerson> = [
|
||||
__typename: 'Company',
|
||||
},
|
||||
phone: '06 12 34 56 78',
|
||||
_commentCount: 1,
|
||||
_commentThreadCount: 1,
|
||||
createdAt: '2023-04-20T13:20:09.158312+00:00',
|
||||
|
||||
city: 'Paris',
|
||||
@ -83,7 +83,7 @@ export const mockedPeopleData: Array<MockedPerson> = [
|
||||
__typename: 'Company',
|
||||
},
|
||||
phone: '06 12 34 56 78',
|
||||
_commentCount: 2,
|
||||
_commentThreadCount: 2,
|
||||
createdAt: '2023-04-20T13:20:09.158312+00:00',
|
||||
|
||||
city: 'Paris',
|
||||
|
1655
front/yarn.lock
1655
front/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,15 @@ export class CommentThreadCountAggregateInput {
|
||||
@HideField()
|
||||
workspaceId?: true;
|
||||
|
||||
@Field(() => Boolean, {nullable:true})
|
||||
authorId?: true;
|
||||
|
||||
@Field(() => Boolean, {nullable:true})
|
||||
body?: true;
|
||||
|
||||
@Field(() => Boolean, {nullable:true})
|
||||
title?: true;
|
||||
|
||||
@HideField()
|
||||
deletedAt?: true;
|
||||
|
||||
|
@ -12,6 +12,15 @@ export class CommentThreadCountAggregate {
|
||||
@HideField()
|
||||
workspaceId!: number;
|
||||
|
||||
@Field(() => Int, {nullable:false})
|
||||
authorId!: number;
|
||||
|
||||
@Field(() => Int, {nullable:false})
|
||||
body!: number;
|
||||
|
||||
@Field(() => Int, {nullable:false})
|
||||
title!: number;
|
||||
|
||||
@HideField()
|
||||
deletedAt!: number;
|
||||
|
||||
|
@ -12,6 +12,15 @@ export class CommentThreadCountOrderByAggregateInput {
|
||||
@HideField()
|
||||
workspaceId?: keyof typeof SortOrder;
|
||||
|
||||
@Field(() => SortOrder, {nullable:true})
|
||||
authorId?: keyof typeof SortOrder;
|
||||
|
||||
@Field(() => SortOrder, {nullable:true})
|
||||
body?: keyof typeof SortOrder;
|
||||
|
||||
@Field(() => SortOrder, {nullable:true})
|
||||
title?: keyof typeof SortOrder;
|
||||
|
||||
@HideField()
|
||||
deletedAt?: keyof typeof SortOrder;
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user