Lucas/t 365 on comment drawer i see a add comment section with severa (#256)
* Added comments and authors on drawer with proper resolving * Fixed generated front graphql from rebase * Fixed comment chip * wip * wip 2 * - Added string typing for DateTime scalar - Refactored user in a recoil state and workspace using it - Added comment creation * Put theme and user state in generic providers * Fix from rebase * Fixed app theme provider removed from storybook * Wip * Fix graphql front * Fixed backend bug * - Added comment fetching in creation mode - Fixed drawer overflows and heights * - Fixed autosize validation button CSS bug * Fixed CSS bug with drawer changing height if overflow * Fixed text input too many event catched and useless error message * Removed console.log * Fixed comment cell chip * Create comment thread on each comment action bar click * Fixed lint * Fixed TopBar height
This commit is contained in:
@ -1092,6 +1092,18 @@ 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, avatarUrl?: string | null } } };
|
export type CreateCommentMutation = { __typename?: 'Mutation', createOneComment: { __typename?: 'Comment', id: string, createdAt: string, body: string, commentThreadId: string, author: { __typename?: 'User', id: string, displayName: string, avatarUrl?: string | null } } };
|
||||||
|
|
||||||
|
export type CreateCommentThreadWithCommentMutationVariables = Exact<{
|
||||||
|
commentThreadId: Scalars['String'];
|
||||||
|
commentText: 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 GetCompanyCommentsCountQueryVariables = Exact<{
|
export type GetCompanyCommentsCountQueryVariables = Exact<{
|
||||||
where?: InputMaybe<CompanyWhereInput>;
|
where?: InputMaybe<CompanyWhereInput>;
|
||||||
}>;
|
}>;
|
||||||
@ -1113,6 +1125,13 @@ 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, avatarUrl?: string | null } }> | null }> };
|
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, avatarUrl?: string | null } }> | 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, avatarUrl?: string | null } }> | null }> };
|
||||||
|
|
||||||
export type GetCompaniesQueryVariables = Exact<{
|
export type GetCompaniesQueryVariables = Exact<{
|
||||||
orderBy?: InputMaybe<Array<CompanyOrderByWithRelationInput> | CompanyOrderByWithRelationInput>;
|
orderBy?: InputMaybe<Array<CompanyOrderByWithRelationInput> | CompanyOrderByWithRelationInput>;
|
||||||
where?: InputMaybe<CompanyWhereInput>;
|
where?: InputMaybe<CompanyWhereInput>;
|
||||||
@ -1285,6 +1304,65 @@ export function useCreateCommentMutation(baseOptions?: Apollo.MutationHookOption
|
|||||||
export type CreateCommentMutationHookResult = ReturnType<typeof useCreateCommentMutation>;
|
export type CreateCommentMutationHookResult = ReturnType<typeof useCreateCommentMutation>;
|
||||||
export type CreateCommentMutationResult = Apollo.MutationResult<CreateCommentMutation>;
|
export type CreateCommentMutationResult = Apollo.MutationResult<CreateCommentMutation>;
|
||||||
export type CreateCommentMutationOptions = Apollo.BaseMutationOptions<CreateCommentMutation, CreateCommentMutationVariables>;
|
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!]!) {
|
||||||
|
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}}}
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
commentThreadTargets {
|
||||||
|
id
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
commentThreadId
|
||||||
|
commentableType
|
||||||
|
commentableId
|
||||||
|
}
|
||||||
|
comments {
|
||||||
|
id
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
body
|
||||||
|
author {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type CreateCommentThreadWithCommentMutationFn = Apollo.MutationFunction<CreateCommentThreadWithCommentMutation, CreateCommentThreadWithCommentMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useCreateCommentThreadWithCommentMutation__
|
||||||
|
*
|
||||||
|
* 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:
|
||||||
|
* - 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({
|
||||||
|
* variables: {
|
||||||
|
* commentThreadId: // value for 'commentThreadId'
|
||||||
|
* commentText: // value for 'commentText'
|
||||||
|
* authorId: // value for 'authorId'
|
||||||
|
* createdAt: // value for 'createdAt'
|
||||||
|
* commentId: // value for 'commentId'
|
||||||
|
* commentThreadTargetArray: // value for 'commentThreadTargetArray'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useCreateCommentThreadWithCommentMutation(baseOptions?: Apollo.MutationHookOptions<CreateCommentThreadWithCommentMutation, CreateCommentThreadWithCommentMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<CreateCommentThreadWithCommentMutation, CreateCommentThreadWithCommentMutationVariables>(CreateCommentThreadWithCommentDocument, options);
|
||||||
|
}
|
||||||
|
export type CreateCommentThreadWithCommentMutationHookResult = ReturnType<typeof useCreateCommentThreadWithCommentMutation>;
|
||||||
|
export type CreateCommentThreadWithCommentMutationResult = Apollo.MutationResult<CreateCommentThreadWithCommentMutation>;
|
||||||
|
export type CreateCommentThreadWithCommentMutationOptions = Apollo.BaseMutationOptions<CreateCommentThreadWithCommentMutation, CreateCommentThreadWithCommentMutationVariables>;
|
||||||
export const GetCompanyCommentsCountDocument = gql`
|
export const GetCompanyCommentsCountDocument = gql`
|
||||||
query GetCompanyCommentsCount($where: CompanyWhereInput) {
|
query GetCompanyCommentsCount($where: CompanyWhereInput) {
|
||||||
companies: findManyCompany(where: $where) {
|
companies: findManyCompany(where: $where) {
|
||||||
@ -1403,6 +1481,52 @@ export function useGetCommentThreadsByTargetsLazyQuery(baseOptions?: Apollo.Lazy
|
|||||||
export type GetCommentThreadsByTargetsQueryHookResult = ReturnType<typeof useGetCommentThreadsByTargetsQuery>;
|
export type GetCommentThreadsByTargetsQueryHookResult = ReturnType<typeof useGetCommentThreadsByTargetsQuery>;
|
||||||
export type GetCommentThreadsByTargetsLazyQueryHookResult = ReturnType<typeof useGetCommentThreadsByTargetsLazyQuery>;
|
export type GetCommentThreadsByTargetsLazyQueryHookResult = ReturnType<typeof useGetCommentThreadsByTargetsLazyQuery>;
|
||||||
export type GetCommentThreadsByTargetsQueryResult = Apollo.QueryResult<GetCommentThreadsByTargetsQuery, GetCommentThreadsByTargetsQueryVariables>;
|
export type GetCommentThreadsByTargetsQueryResult = Apollo.QueryResult<GetCommentThreadsByTargetsQuery, GetCommentThreadsByTargetsQueryVariables>;
|
||||||
|
export const GetCommentThreadDocument = gql`
|
||||||
|
query GetCommentThread($commentThreadId: String!) {
|
||||||
|
findManyCommentThreads(where: {id: {equals: $commentThreadId}}) {
|
||||||
|
id
|
||||||
|
comments {
|
||||||
|
id
|
||||||
|
body
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
author {
|
||||||
|
id
|
||||||
|
displayName
|
||||||
|
avatarUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useGetCommentThreadQuery__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `useGetCommentThreadQuery` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useGetCommentThreadQuery` 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 } = useGetCommentThreadQuery({
|
||||||
|
* variables: {
|
||||||
|
* commentThreadId: // value for 'commentThreadId'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useGetCommentThreadQuery(baseOptions: Apollo.QueryHookOptions<GetCommentThreadQuery, GetCommentThreadQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useQuery<GetCommentThreadQuery, GetCommentThreadQueryVariables>(GetCommentThreadDocument, options);
|
||||||
|
}
|
||||||
|
export function useGetCommentThreadLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetCommentThreadQuery, GetCommentThreadQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useLazyQuery<GetCommentThreadQuery, GetCommentThreadQueryVariables>(GetCommentThreadDocument, options);
|
||||||
|
}
|
||||||
|
export type GetCommentThreadQueryHookResult = ReturnType<typeof useGetCommentThreadQuery>;
|
||||||
|
export type GetCommentThreadLazyQueryHookResult = ReturnType<typeof useGetCommentThreadLazyQuery>;
|
||||||
|
export type GetCommentThreadQueryResult = Apollo.QueryResult<GetCommentThreadQuery, GetCommentThreadQueryVariables>;
|
||||||
export const GetCompaniesDocument = gql`
|
export const GetCompaniesDocument = gql`
|
||||||
query GetCompanies($orderBy: [CompanyOrderByWithRelationInput!], $where: CompanyWhereInput) {
|
query GetCompanies($orderBy: [CompanyOrderByWithRelationInput!], $where: CompanyWhereInput) {
|
||||||
companies: findManyCompany(orderBy: $orderBy, where: $where) {
|
companies: findManyCompany(orderBy: $orderBy, where: $where) {
|
||||||
|
|||||||
@ -2,18 +2,28 @@ import styled from '@emotion/styled';
|
|||||||
|
|
||||||
import { CommentChip, CommentChipProps } from './CommentChip';
|
import { CommentChip, CommentChipProps } from './CommentChip';
|
||||||
|
|
||||||
|
// TODO: tie those fixed values to the other components in the cell
|
||||||
const StyledCellWrapper = styled.div`
|
const StyledCellWrapper = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
right: -46px;
|
||||||
|
top: 3px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledCommentChipContainer = styled.div`
|
||||||
position: relative;
|
position: relative;
|
||||||
right: 34px;
|
right: 50px;
|
||||||
top: -13px;
|
width: 50px;
|
||||||
width: 0;
|
|
||||||
height: 0;
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export function CellCommentChip(props: CommentChipProps) {
|
export function CellCommentChip(props: CommentChipProps) {
|
||||||
return (
|
return (
|
||||||
<StyledCellWrapper>
|
<StyledCellWrapper>
|
||||||
<CommentChip {...props} />
|
<StyledCommentChipContainer>
|
||||||
|
<CommentChip {...props} />
|
||||||
|
</StyledCommentChipContainer>
|
||||||
</StyledCellWrapper>
|
</StyledCellWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@ export type CommentChipProps = {
|
|||||||
|
|
||||||
const StyledChip = styled.div`
|
const StyledChip = styled.div`
|
||||||
height: 26px;
|
height: 26px;
|
||||||
width: fit-content;
|
max-width: 42px;
|
||||||
|
|
||||||
padding-left: 4px;
|
padding-left: 4px;
|
||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
|
|||||||
@ -37,6 +37,7 @@ const StyledThreadItemListContainer = styled.div`
|
|||||||
|
|
||||||
max-height: 400px;
|
max-height: 400px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
gap: ${(props) => props.theme.spacing(4)};
|
gap: ${(props) => props.theme.spacing(4)};
|
||||||
`;
|
`;
|
||||||
@ -46,6 +47,10 @@ export function CommentThread({ commentThread }: OwnProps) {
|
|||||||
const currentUser = useRecoilValue(currentUserState);
|
const currentUser = useRecoilValue(currentUserState);
|
||||||
|
|
||||||
function handleSendComment(commentText: string) {
|
function handleSendComment(commentText: string) {
|
||||||
|
if (!isNonEmptyString(commentText)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!isDefined(currentUser)) {
|
if (!isDefined(currentUser)) {
|
||||||
logError(
|
logError(
|
||||||
'In handleSendComment, currentUser is not defined, this should not happen.',
|
'In handleSendComment, currentUser is not defined, this should not happen.',
|
||||||
@ -53,35 +58,27 @@ export function CommentThread({ commentThread }: OwnProps) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isNonEmptyString(commentText)) {
|
createCommentMutation({
|
||||||
logError(
|
variables: {
|
||||||
'In handleSendComment, trying to send empty text, this should not happen.',
|
commentId: v4(),
|
||||||
);
|
authorId: currentUser.id,
|
||||||
return;
|
commentThreadId: commentThread.id,
|
||||||
}
|
commentText,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
if (isDefined(currentUser)) {
|
},
|
||||||
createCommentMutation({
|
// TODO: find a way to have this configuration dynamic and typed
|
||||||
variables: {
|
// Also it cannot refetch queries than are not in the cache
|
||||||
commentId: v4(),
|
refetchQueries: [
|
||||||
authorId: currentUser.id,
|
'GetCommentThreadsByTargets',
|
||||||
commentThreadId: commentThread.id,
|
'GetPeopleCommentsCount',
|
||||||
commentText,
|
'GetCompanyCommentsCount',
|
||||||
createdAt: new Date().toISOString(),
|
],
|
||||||
},
|
onError: (error) => {
|
||||||
// TODO: find a way to have this configuration dynamic and typed
|
logError(
|
||||||
refetchQueries: [
|
`In handleSendComment, createCommentMutation onError, error: ${error}`,
|
||||||
'GetCommentThreadsByTargets',
|
);
|
||||||
'GetPeopleCommentsCount',
|
},
|
||||||
'GetCompanyCommentsCount',
|
});
|
||||||
],
|
|
||||||
onError: (error) => {
|
|
||||||
logError(
|
|
||||||
`In handleSendComment, createCommentMutation onError, error: ${error}`,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -91,7 +88,7 @@ export function CommentThread({ commentThread }: OwnProps) {
|
|||||||
<CommentThreadItem key={comment.id} comment={comment} />
|
<CommentThreadItem key={comment.id} comment={comment} />
|
||||||
))}
|
))}
|
||||||
</StyledThreadItemListContainer>
|
</StyledThreadItemListContainer>
|
||||||
<AutosizeTextInput onSend={handleSendComment} />
|
<AutosizeTextInput onValidate={handleSendComment} />
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,140 @@
|
|||||||
|
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 { 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,
|
||||||
|
useCreateCommentThreadWithCommentMutation,
|
||||||
|
useGetCommentThreadQuery,
|
||||||
|
} from '~/generated/graphql';
|
||||||
|
|
||||||
|
import { CommentThreadItem } from './CommentThreadItem';
|
||||||
|
|
||||||
|
const StyledContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
max-height: calc(100% - 16px);
|
||||||
|
|
||||||
|
gap: ${(props) => props.theme.spacing(4)};
|
||||||
|
padding: ${(props) => props.theme.spacing(2)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledThreadItemListContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
overflow: auto;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
gap: ${(props) => props.theme.spacing(4)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export function CommentThreadCreateMode() {
|
||||||
|
const [commentableEntityArray] = useRecoilState(commentableEntityArrayState);
|
||||||
|
|
||||||
|
const [createdCommmentThreadId, setCreatedCommentThreadId] = useRecoilState(
|
||||||
|
createdCommentThreadIdState,
|
||||||
|
);
|
||||||
|
|
||||||
|
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: ['GetCommentThread'],
|
||||||
|
onCompleted(data) {
|
||||||
|
setCreatedCommentThreadId(data.createOneCommentThread.id);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
createCommentMutation({
|
||||||
|
variables: {
|
||||||
|
commentId: v4(),
|
||||||
|
authorId: currentUser.id,
|
||||||
|
commentThreadId: createdCommmentThreadId,
|
||||||
|
commentText,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
// TODO: find a way to have this configuration dynamic and typed
|
||||||
|
refetchQueries: [
|
||||||
|
'GetCommentThread',
|
||||||
|
'GetPeopleCommentsCount',
|
||||||
|
'GetCompanyCommentsCount',
|
||||||
|
],
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
export function RightDrawerCreateCommentThread() {
|
||||||
|
return (
|
||||||
|
<RightDrawerPage>
|
||||||
|
<RightDrawerTopBar title="New comment" />
|
||||||
|
<RightDrawerBody>
|
||||||
|
<CommentThreadCreateMode />
|
||||||
|
</RightDrawerBody>
|
||||||
|
</RightDrawerPage>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
import { selectedRowIdsState } from '@/ui/tables/states/selectedRowIdsState';
|
||||||
|
import { CommentableType } from '~/generated/graphql';
|
||||||
|
|
||||||
|
import { useOpenRightDrawer } from '../../ui/layout/right-drawer/hooks/useOpenRightDrawer';
|
||||||
|
import { commentableEntityArrayState } from '../states/commentableEntityArrayState';
|
||||||
|
import { createdCommentThreadIdState } from '../states/createdCommentThreadIdState';
|
||||||
|
import { CommentableEntity } from '../types/CommentableEntity';
|
||||||
|
|
||||||
|
export function useOpenCreateCommentThreadDrawerForSelectedRowIds() {
|
||||||
|
const openRightDrawer = useOpenRightDrawer();
|
||||||
|
|
||||||
|
const [, setCommentableEntityArray] = useRecoilState(
|
||||||
|
commentableEntityArrayState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [, setCreatedCommentThreadId] = useRecoilState(
|
||||||
|
createdCommentThreadIdState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectedPeopleIds = useRecoilValue(selectedRowIdsState);
|
||||||
|
|
||||||
|
return function openCreateCommentDrawerForSelectedRowIds(
|
||||||
|
entityType: CommentableType,
|
||||||
|
) {
|
||||||
|
const commentableEntityArray: CommentableEntity[] = selectedPeopleIds.map(
|
||||||
|
(id) => ({
|
||||||
|
type: entityType,
|
||||||
|
id,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
setCreatedCommentThreadId(null);
|
||||||
|
setCommentableEntityArray(commentableEntityArray);
|
||||||
|
openRightDrawer('create-comment-thread');
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -29,3 +29,56 @@ export const CREATE_COMMENT = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const CREATE_COMMENT_THREAD_WITH_COMMENT = gql`
|
||||||
|
mutation CreateCommentThreadWithComment(
|
||||||
|
$commentThreadId: String!
|
||||||
|
$commentText: String!
|
||||||
|
$authorId: String!
|
||||||
|
$createdAt: DateTime!
|
||||||
|
$commentId: String!
|
||||||
|
$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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
commentThreadTargets {
|
||||||
|
id
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
commentThreadId
|
||||||
|
commentableType
|
||||||
|
commentableId
|
||||||
|
}
|
||||||
|
comments {
|
||||||
|
id
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
body
|
||||||
|
author {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|||||||
@ -59,3 +59,22 @@ export const GET_COMMENT_THREADS_BY_TARGETS = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const GET_COMMENT_THREAD = gql`
|
||||||
|
query GetCommentThread($commentThreadId: String!) {
|
||||||
|
findManyCommentThreads(where: { id: { equals: $commentThreadId } }) {
|
||||||
|
id
|
||||||
|
comments {
|
||||||
|
id
|
||||||
|
body
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
author {
|
||||||
|
id
|
||||||
|
displayName
|
||||||
|
avatarUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { atom } from 'recoil';
|
||||||
|
|
||||||
|
export const createdCommentThreadIdState = atom<string | null>({
|
||||||
|
key: 'comments/created-comment-thread-id',
|
||||||
|
default: null,
|
||||||
|
});
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { CommentableType } from '~/generated/graphql';
|
import { CommentableType } from '~/generated/graphql';
|
||||||
|
|
||||||
export type CommentableEntity = {
|
export type CommentableEntity = {
|
||||||
type: keyof typeof CommentableType;
|
type: CommentableType;
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { useOpenCommentRightDrawer } from '@/comments/hooks/useOpenCommentRightD
|
|||||||
import { useCompanyCommentsCountQuery } from '@/comments/services';
|
import { useCompanyCommentsCountQuery } from '@/comments/services';
|
||||||
import EditableChip from '@/ui/components/editable-cell/types/EditableChip';
|
import EditableChip from '@/ui/components/editable-cell/types/EditableChip';
|
||||||
import { getLogoUrlFromDomainName } from '@/utils/utils';
|
import { getLogoUrlFromDomainName } from '@/utils/utils';
|
||||||
|
import { CommentableType } from '~/generated/graphql';
|
||||||
|
|
||||||
import { Company } from '../interfaces/company.interface';
|
import { Company } from '../interfaces/company.interface';
|
||||||
import { updateCompany } from '../services';
|
import { updateCompany } from '../services';
|
||||||
@ -22,7 +23,7 @@ export function CompanyEditableNameChipCell({ company }: OwnProps) {
|
|||||||
|
|
||||||
openCommentRightDrawer([
|
openCommentRightDrawer([
|
||||||
{
|
{
|
||||||
type: 'Company',
|
type: CommentableType.Company,
|
||||||
id: company.id,
|
id: company.id,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import styled from '@emotion/styled';
|
|||||||
import { CellCommentChip } from '@/comments/components/comments/CellCommentChip';
|
import { CellCommentChip } from '@/comments/components/comments/CellCommentChip';
|
||||||
import { useOpenCommentRightDrawer } from '@/comments/hooks/useOpenCommentRightDrawer';
|
import { useOpenCommentRightDrawer } from '@/comments/hooks/useOpenCommentRightDrawer';
|
||||||
import { EditableDoubleText } from '@/ui/components/editable-cell/types/EditableDoubleText';
|
import { EditableDoubleText } from '@/ui/components/editable-cell/types/EditableDoubleText';
|
||||||
|
import { CommentableType } from '~/generated/graphql';
|
||||||
|
|
||||||
import { usePeopleCommentsCountQuery } from '../../comments/services';
|
import { usePeopleCommentsCountQuery } from '../../comments/services';
|
||||||
|
|
||||||
@ -49,7 +50,7 @@ export function EditablePeopleFullName({
|
|||||||
|
|
||||||
openCommentRightDrawer([
|
openCommentRightDrawer([
|
||||||
{
|
{
|
||||||
type: 'Person',
|
type: CommentableType.Person,
|
||||||
id: personId,
|
id: personId,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import {
|
|||||||
import { SearchConfigType } from '@/search/interfaces/interface';
|
import { SearchConfigType } from '@/search/interfaces/interface';
|
||||||
import { SEARCH_COMPANY_QUERY } from '@/search/services/search';
|
import { SEARCH_COMPANY_QUERY } from '@/search/services/search';
|
||||||
import { EditableRelation } from '@/ui/components/editable-cell/types/EditableRelation';
|
import { EditableRelation } from '@/ui/components/editable-cell/types/EditableRelation';
|
||||||
|
import { logError } from '@/utils/logs/logError';
|
||||||
import { getLogoUrlFromDomainName } from '@/utils/utils';
|
import { getLogoUrlFromDomainName } from '@/utils/utils';
|
||||||
import {
|
import {
|
||||||
QueryMode,
|
QueryMode,
|
||||||
@ -57,7 +58,7 @@ export function PeopleCompanyCell({ people }: OwnProps) {
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// TODO: handle error better
|
// TODO: handle error better
|
||||||
console.log(error);
|
logError(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsCreating(false);
|
setIsCreating(false);
|
||||||
|
|||||||
@ -5,16 +5,19 @@ import { HiArrowSmRight } from 'react-icons/hi';
|
|||||||
import TextareaAutosize from 'react-textarea-autosize';
|
import TextareaAutosize from 'react-textarea-autosize';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { IconButton } from '../buttons/IconButton';
|
import { IconButton } from '@/ui/components/buttons/IconButton';
|
||||||
|
|
||||||
|
const MAX_ROWS = 5;
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
onSend?: (text: string) => void;
|
onValidate?: (text: string) => void;
|
||||||
|
minRows?: number;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
min-height: 32px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -43,14 +46,21 @@ const StyledTextArea = styled(TextareaAutosize)`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
// TODO: this messes with the layout, fix it
|
||||||
const StyledBottomRightIconButton = styled.div`
|
const StyledBottomRightIconButton = styled.div`
|
||||||
width: 0px;
|
width: 0px;
|
||||||
position: relative;
|
position: relative;
|
||||||
top: calc(100% - 26.5px);
|
top: calc(100% - 26.5px);
|
||||||
right: 26px;
|
right: 26px;
|
||||||
|
height: 0;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export function AutosizeTextInput({ placeholder, onSend }: OwnProps) {
|
export function AutosizeTextInput({
|
||||||
|
placeholder,
|
||||||
|
onValidate,
|
||||||
|
minRows = 1,
|
||||||
|
}: OwnProps) {
|
||||||
|
const [isFocused, setIsFocused] = useState(false);
|
||||||
const [text, setText] = useState('');
|
const [text, setText] = useState('');
|
||||||
|
|
||||||
const isSendButtonDisabled = !text;
|
const isSendButtonDisabled = !text;
|
||||||
@ -58,12 +68,12 @@ export function AutosizeTextInput({ placeholder, onSend }: OwnProps) {
|
|||||||
useHotkeys(
|
useHotkeys(
|
||||||
['shift+enter', 'enter'],
|
['shift+enter', 'enter'],
|
||||||
(event: KeyboardEvent, handler: HotkeysEvent) => {
|
(event: KeyboardEvent, handler: HotkeysEvent) => {
|
||||||
if (handler.shift) {
|
if (handler.shift || !isFocused) {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
onSend?.(text);
|
onValidate?.(text);
|
||||||
|
|
||||||
setText('');
|
setText('');
|
||||||
}
|
}
|
||||||
@ -72,12 +82,16 @@ export function AutosizeTextInput({ placeholder, onSend }: OwnProps) {
|
|||||||
enableOnContentEditable: true,
|
enableOnContentEditable: true,
|
||||||
enableOnFormTags: true,
|
enableOnFormTags: true,
|
||||||
},
|
},
|
||||||
[onSend, text, setText],
|
[onValidate, text, setText, isFocused],
|
||||||
);
|
);
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
'esc',
|
'esc',
|
||||||
(event: KeyboardEvent) => {
|
(event: KeyboardEvent) => {
|
||||||
|
if (!isFocused) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
setText('');
|
setText('');
|
||||||
@ -86,7 +100,7 @@ export function AutosizeTextInput({ placeholder, onSend }: OwnProps) {
|
|||||||
enableOnContentEditable: true,
|
enableOnContentEditable: true,
|
||||||
enableOnFormTags: true,
|
enableOnFormTags: true,
|
||||||
},
|
},
|
||||||
[onSend, setText],
|
[onValidate, setText, isFocused],
|
||||||
);
|
);
|
||||||
|
|
||||||
function handleInputChange(event: React.FormEvent<HTMLTextAreaElement>) {
|
function handleInputChange(event: React.FormEvent<HTMLTextAreaElement>) {
|
||||||
@ -96,19 +110,24 @@ export function AutosizeTextInput({ placeholder, onSend }: OwnProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleOnClickSendButton() {
|
function handleOnClickSendButton() {
|
||||||
onSend?.(text);
|
onValidate?.(text);
|
||||||
|
|
||||||
setText('');
|
setText('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const computedMinRows = minRows > MAX_ROWS ? MAX_ROWS : minRows;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<StyledTextArea
|
<StyledTextArea
|
||||||
placeholder={placeholder || 'Write something...'}
|
placeholder={placeholder || 'Write something...'}
|
||||||
maxRows={5}
|
maxRows={MAX_ROWS}
|
||||||
|
minRows={computedMinRows}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
value={text}
|
value={text}
|
||||||
|
onFocus={() => setIsFocused(true)}
|
||||||
|
onBlur={() => setIsFocused(false)}
|
||||||
/>
|
/>
|
||||||
<StyledBottomRightIconButton>
|
<StyledBottomRightIconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|||||||
@ -7,11 +7,6 @@ import { AutosizeTextInput } from '../AutosizeTextInput';
|
|||||||
const meta: Meta<typeof AutosizeTextInput> = {
|
const meta: Meta<typeof AutosizeTextInput> = {
|
||||||
title: 'Components/Common/AutosizeTextInput',
|
title: 'Components/Common/AutosizeTextInput',
|
||||||
component: AutosizeTextInput,
|
component: AutosizeTextInput,
|
||||||
argTypes: {
|
|
||||||
onSend: {
|
|
||||||
action: 'onSend',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
|
|||||||
@ -1,23 +1,17 @@
|
|||||||
import { FaRegComment } from 'react-icons/fa';
|
import { FaRegComment } from 'react-icons/fa';
|
||||||
|
|
||||||
import { useOpenRightDrawer } from '@/ui/layout/right-drawer/hooks/useOpenRightDrawer';
|
|
||||||
|
|
||||||
import { EntityTableActionBarButton } from './EntityTableActionBarButton';
|
import { EntityTableActionBarButton } from './EntityTableActionBarButton';
|
||||||
|
|
||||||
export function TableActionBarButtonToggleComments() {
|
type OwnProps = {
|
||||||
// TODO: here it would be nice to access the table context
|
onClick: () => void;
|
||||||
// But let's see when we have custom entities and properties
|
};
|
||||||
const openRightDrawer = useOpenRightDrawer();
|
|
||||||
|
|
||||||
async function handleButtonClick() {
|
|
||||||
openRightDrawer('comments');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
export function TableActionBarButtonToggleComments({ onClick }: OwnProps) {
|
||||||
return (
|
return (
|
||||||
<EntityTableActionBarButton
|
<EntityTableActionBarButton
|
||||||
label="Comment"
|
label="Comment"
|
||||||
icon={<FaRegComment size={16} />}
|
icon={<FaRegComment size={16} />}
|
||||||
onClick={handleButtonClick}
|
onClick={onClick}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { useRecoilState } from 'recoil';
|
|||||||
import { Panel } from '../Panel';
|
import { Panel } from '../Panel';
|
||||||
import { RightDrawer } from '../right-drawer/components/RightDrawer';
|
import { RightDrawer } from '../right-drawer/components/RightDrawer';
|
||||||
import { isRightDrawerOpenState } from '../right-drawer/states/isRightDrawerOpenState';
|
import { isRightDrawerOpenState } from '../right-drawer/states/isRightDrawerOpenState';
|
||||||
import { TopBar } from '../top-bar/TopBar';
|
import { TOP_BAR_MIN_HEIGHT, TopBar } from '../top-bar/TopBar';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
children: JSX.Element;
|
children: JSX.Element;
|
||||||
@ -20,13 +20,14 @@ const StyledContainer = styled.div`
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const TOPBAR_HEIGHT = '48px';
|
|
||||||
|
|
||||||
const MainContainer = styled.div`
|
const MainContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
width: calc(100% - ${(props) => props.theme.spacing(3)});
|
width: calc(100% - ${(props) => props.theme.spacing(3)});
|
||||||
height: calc(100% - ${TOPBAR_HEIGHT} - ${(props) => props.theme.spacing(3)});
|
height: calc(
|
||||||
|
100% - ${TOP_BAR_MIN_HEIGHT} - ${(props) => props.theme.spacing(2)} -
|
||||||
|
${(props) => props.theme.spacing(5)}
|
||||||
|
);
|
||||||
background: ${(props) => props.theme.noisyBackground};
|
background: ${(props) => props.theme.noisyBackground};
|
||||||
padding-right: ${(props) => props.theme.spacing(3)};
|
padding-right: ${(props) => props.theme.spacing(3)};
|
||||||
padding-bottom: ${(props) => props.theme.spacing(3)};
|
padding-bottom: ${(props) => props.theme.spacing(3)};
|
||||||
|
|||||||
@ -3,4 +3,5 @@ import styled from '@emotion/styled';
|
|||||||
export const RightDrawerBody = styled.div`
|
export const RightDrawerBody = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
overflow: auto;
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { RightDrawerComments } from '@/comments/components/comments/RightDrawerComments';
|
import { RightDrawerComments } from '@/comments/components/comments/RightDrawerComments';
|
||||||
|
import { RightDrawerCreateCommentThread } from '@/comments/components/comments/RightDrawerCreateCommentThread';
|
||||||
import { isDefined } from '@/utils/type-guards/isDefined';
|
import { isDefined } from '@/utils/type-guards/isDefined';
|
||||||
|
|
||||||
import { rightDrawerPageState } from '../states/rightDrawerPageState';
|
import { rightDrawerPageState } from '../states/rightDrawerPageState';
|
||||||
@ -12,5 +13,11 @@ export function RightDrawerRouter() {
|
|||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return rightDrawerPage === 'comments' ? <RightDrawerComments /> : <></>;
|
return rightDrawerPage === 'comments' ? (
|
||||||
|
<RightDrawerComments />
|
||||||
|
) : rightDrawerPage === 'create-comment-thread' ? (
|
||||||
|
<RightDrawerCreateCommentThread />
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { RightDrawerTopBarCloseButton } from './RightDrawerTopBarCloseButton';
|
|||||||
const StyledRightDrawerTopBar = styled.div`
|
const StyledRightDrawerTopBar = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
height: 40px;
|
min-height: 40px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
|
|||||||
@ -4,5 +4,5 @@ import { RightDrawerPage } from '../types/RightDrawerPage';
|
|||||||
|
|
||||||
export const rightDrawerPageState = atom<RightDrawerPage | null>({
|
export const rightDrawerPageState = atom<RightDrawerPage | null>({
|
||||||
key: 'ui/layout/right-drawer-page',
|
key: 'ui/layout/right-drawer-page',
|
||||||
default: 'comments',
|
default: null,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
export type RightDrawerPage = 'comments';
|
export type RightDrawerPage = 'comments' | 'create-comment-thread';
|
||||||
|
|||||||
@ -2,13 +2,15 @@ import { ReactNode } from 'react';
|
|||||||
import { TbPlus } from 'react-icons/tb';
|
import { TbPlus } from 'react-icons/tb';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
export const TOP_BAR_MIN_HEIGHT = '40px';
|
||||||
|
|
||||||
const TopBarContainer = styled.div`
|
const TopBarContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
height: 38px;
|
min-height: ${TOP_BAR_MIN_HEIGHT};
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: ${(props) => props.theme.noisyBackground};
|
background: ${(props) => props.theme.noisyBackground};
|
||||||
padding: 8px;
|
padding: ${(props) => props.theme.spacing(2)};
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: ${(props) => props.theme.text80};
|
color: ${(props) => props.theme.text80};
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -20,12 +20,12 @@ import {
|
|||||||
} from '@/filters-and-sorts/helpers';
|
} from '@/filters-and-sorts/helpers';
|
||||||
import { SelectedFilterType } from '@/filters-and-sorts/interfaces/filters/interface';
|
import { SelectedFilterType } from '@/filters-and-sorts/interfaces/filters/interface';
|
||||||
import { EntityTableActionBar } from '@/ui/components/table/action-bar/EntityTableActionBar';
|
import { EntityTableActionBar } from '@/ui/components/table/action-bar/EntityTableActionBar';
|
||||||
import { TableActionBarButtonToggleComments } from '@/ui/components/table/action-bar/TableActionBarButtonOpenComments';
|
|
||||||
import { EntityTable } from '@/ui/components/table/EntityTable';
|
import { EntityTable } from '@/ui/components/table/EntityTable';
|
||||||
import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer';
|
import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer';
|
||||||
import { BoolExpType } from '@/utils/interfaces/generic.interface';
|
import { BoolExpType } from '@/utils/interfaces/generic.interface';
|
||||||
import { CompanyOrderByWithRelationInput as Companies_Order_By } from '~/generated/graphql';
|
import { CompanyOrderByWithRelationInput as Companies_Order_By } from '~/generated/graphql';
|
||||||
|
|
||||||
|
import { TableActionBarButtonCreateCommentThreadCompany } from './table/TableActionBarButtonCreateCommentThreadCompany';
|
||||||
import { TableActionBarButtonDeleteCompanies } from './table/TableActionBarButtonDeleteCompanies';
|
import { TableActionBarButtonDeleteCompanies } from './table/TableActionBarButtonDeleteCompanies';
|
||||||
import { useCompaniesColumns } from './companies-columns';
|
import { useCompaniesColumns } from './companies-columns';
|
||||||
import { availableFilters } from './companies-filters';
|
import { availableFilters } from './companies-filters';
|
||||||
@ -93,7 +93,7 @@ export function Companies() {
|
|||||||
/>
|
/>
|
||||||
</StyledCompaniesContainer>
|
</StyledCompaniesContainer>
|
||||||
<EntityTableActionBar>
|
<EntityTableActionBar>
|
||||||
<TableActionBarButtonToggleComments />
|
<TableActionBarButtonCreateCommentThreadCompany />
|
||||||
<TableActionBarButtonDeleteCompanies />
|
<TableActionBarButtonDeleteCompanies />
|
||||||
</EntityTableActionBar>
|
</EntityTableActionBar>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -0,0 +1,14 @@
|
|||||||
|
import { useOpenCreateCommentThreadDrawerForSelectedRowIds } from '@/comments/hooks/useOpenCreateCommentDrawerForSelectedRowIds';
|
||||||
|
import { TableActionBarButtonToggleComments } from '@/ui/components/table/action-bar/TableActionBarButtonOpenComments';
|
||||||
|
import { CommentableType } from '~/generated/graphql';
|
||||||
|
|
||||||
|
export function TableActionBarButtonCreateCommentThreadCompany() {
|
||||||
|
const openCreateCommentThreadRightDrawer =
|
||||||
|
useOpenCreateCommentThreadDrawerForSelectedRowIds();
|
||||||
|
|
||||||
|
async function handleButtonClick() {
|
||||||
|
openCreateCommentThreadRightDrawer(CommentableType.Company);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <TableActionBarButtonToggleComments onClick={handleButtonClick} />;
|
||||||
|
}
|
||||||
@ -17,11 +17,11 @@ import {
|
|||||||
usePeopleQuery,
|
usePeopleQuery,
|
||||||
} from '@/people/services';
|
} from '@/people/services';
|
||||||
import { EntityTableActionBar } from '@/ui/components/table/action-bar/EntityTableActionBar';
|
import { EntityTableActionBar } from '@/ui/components/table/action-bar/EntityTableActionBar';
|
||||||
import { TableActionBarButtonToggleComments } from '@/ui/components/table/action-bar/TableActionBarButtonOpenComments';
|
|
||||||
import { EntityTable } from '@/ui/components/table/EntityTable';
|
import { EntityTable } from '@/ui/components/table/EntityTable';
|
||||||
import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer';
|
import { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer';
|
||||||
import { BoolExpType } from '@/utils/interfaces/generic.interface';
|
import { BoolExpType } from '@/utils/interfaces/generic.interface';
|
||||||
|
|
||||||
|
import { TableActionBarButtonCreateCommentThreadPeople } from './table/TableActionBarButtonCreateCommentThreadPeople';
|
||||||
import { TableActionBarButtonDeletePeople } from './table/TableActionBarButtonDeletePeople';
|
import { TableActionBarButtonDeletePeople } from './table/TableActionBarButtonDeletePeople';
|
||||||
import { usePeopleColumns } from './people-columns';
|
import { usePeopleColumns } from './people-columns';
|
||||||
import { availableFilters } from './people-filters';
|
import { availableFilters } from './people-filters';
|
||||||
@ -91,7 +91,7 @@ export function People() {
|
|||||||
/>
|
/>
|
||||||
</StyledPeopleContainer>
|
</StyledPeopleContainer>
|
||||||
<EntityTableActionBar>
|
<EntityTableActionBar>
|
||||||
<TableActionBarButtonToggleComments />
|
<TableActionBarButtonCreateCommentThreadPeople />
|
||||||
<TableActionBarButtonDeletePeople />
|
<TableActionBarButtonDeletePeople />
|
||||||
</EntityTableActionBar>
|
</EntityTableActionBar>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -0,0 +1,14 @@
|
|||||||
|
import { useOpenCreateCommentThreadDrawerForSelectedRowIds } from '@/comments/hooks/useOpenCreateCommentDrawerForSelectedRowIds';
|
||||||
|
import { TableActionBarButtonToggleComments } from '@/ui/components/table/action-bar/TableActionBarButtonOpenComments';
|
||||||
|
import { CommentableType } from '~/generated/graphql';
|
||||||
|
|
||||||
|
export function TableActionBarButtonCreateCommentThreadPeople() {
|
||||||
|
const openCreateCommentThreadRightDrawer =
|
||||||
|
useOpenCreateCommentThreadDrawerForSelectedRowIds();
|
||||||
|
|
||||||
|
async function handleButtonClick() {
|
||||||
|
openCreateCommentThreadRightDrawer(CommentableType.Person);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <TableActionBarButtonToggleComments onClick={handleButtonClick} />;
|
||||||
|
}
|
||||||
@ -32,13 +32,48 @@ export class CommentThreadResolver {
|
|||||||
...{ workspaceId: workspace.id },
|
...{ workspaceId: workspace.id },
|
||||||
}))
|
}))
|
||||||
: [];
|
: [];
|
||||||
return this.prismaService.commentThread.create({
|
|
||||||
|
const createdCommentThread = await this.prismaService.commentThread.create({
|
||||||
data: {
|
data: {
|
||||||
...args.data,
|
...args.data,
|
||||||
|
...{ commentThreadTargets: undefined },
|
||||||
...{ comments: { createMany: { data: newCommentData } } },
|
...{ comments: { createMany: { data: newCommentData } } },
|
||||||
...{ workspace: { connect: { id: workspace.id } } },
|
...{ workspace: { connect: { id: workspace.id } } },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (args.data.commentThreadTargets?.createMany?.data) {
|
||||||
|
await this.prismaService.commentThreadTarget.createMany({
|
||||||
|
data: args.data.commentThreadTargets?.createMany?.data?.map(
|
||||||
|
(target) => ({
|
||||||
|
...target,
|
||||||
|
commentThreadId: args.data.id,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
skipDuplicates:
|
||||||
|
args.data.commentThreadTargets?.createMany?.skipDuplicates ?? false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return await this.prismaService.commentThread.update({
|
||||||
|
where: { id: args.data.id },
|
||||||
|
data: {
|
||||||
|
commentThreadTargets: {
|
||||||
|
connect: args.data.commentThreadTargets?.connect,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return createdCommentThread;
|
||||||
|
|
||||||
|
// return this.prismaService.commentThread.create({
|
||||||
|
// data: {
|
||||||
|
// ...args.data,
|
||||||
|
// ...{ commentThreadTargets: undefined },
|
||||||
|
// ...{ comments: { createMany: { data: newCommentData } } },
|
||||||
|
// ...{ workspace: { connect: { id: workspace.id } } },
|
||||||
|
// },
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
@Query(() => [CommentThread])
|
@Query(() => [CommentThread])
|
||||||
|
|||||||
@ -14,12 +14,13 @@ export class CreateOneCommentThreadGuard implements CanActivate {
|
|||||||
|
|
||||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
const gqlContext = GqlExecutionContext.create(context);
|
const gqlContext = GqlExecutionContext.create(context);
|
||||||
|
// TODO: type request
|
||||||
const request = gqlContext.getContext().req;
|
const request = gqlContext.getContext().req;
|
||||||
const args = gqlContext.getArgs();
|
const args = gqlContext.getArgs();
|
||||||
|
|
||||||
const targets = args.data?.commentThreadTargets?.createMany?.data;
|
const targets = args.data?.commentThreadTargets?.createMany?.data;
|
||||||
const comments = args.data?.comments?.createMany?.data;
|
const comments = args.data?.comments?.createMany?.data;
|
||||||
const workspaceId = await request.workspace;
|
const workspace = await request.workspace;
|
||||||
|
|
||||||
if (!targets || targets.length === 0) {
|
if (!targets || targets.length === 0) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
@ -52,7 +53,7 @@ export class CreateOneCommentThreadGuard implements CanActivate {
|
|||||||
where: { id: target.commentableId },
|
where: { id: target.commentableId },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!targetEntity || targetEntity.workspaceId !== workspaceId) {
|
if (!targetEntity || targetEntity.workspaceId !== workspace.id) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
{ reason: 'CommentThreadTarget not found' },
|
{ reason: 'CommentThreadTarget not found' },
|
||||||
HttpStatus.NOT_FOUND,
|
HttpStatus.NOT_FOUND,
|
||||||
@ -90,10 +91,10 @@ export class CreateOneCommentThreadGuard implements CanActivate {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
!userWorkspaceMember ||
|
!userWorkspaceMember ||
|
||||||
userWorkspaceMember.workspaceId !== workspaceId
|
userWorkspaceMember.workspaceId !== workspace.id
|
||||||
) {
|
) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
{ reason: 'Comment.authorId not found' },
|
{ reason: 'userWorkspaceMember.workspaceId not found' },
|
||||||
HttpStatus.NOT_FOUND,
|
HttpStatus.NOT_FOUND,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,6 @@ export class UpdateOneGuard implements CanActivate {
|
|||||||
const entity = gqlContext.getArgByIndex(3).returnType?.name;
|
const entity = gqlContext.getArgByIndex(3).returnType?.name;
|
||||||
const args = gqlContext.getArgs();
|
const args = gqlContext.getArgs();
|
||||||
|
|
||||||
console.log(args.data);
|
|
||||||
if (!entity || !args.where?.id) {
|
if (!entity || !args.where?.id) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
{ reason: 'Invalid Request' },
|
{ reason: 'Invalid Request' },
|
||||||
|
|||||||
Reference in New Issue
Block a user