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:
Lucas Bordeau
2023-06-08 17:40:58 +02:00
committed by GitHub
parent 49a99c8ae6
commit 4727c00a0a
31 changed files with 574 additions and 86 deletions

View File

@ -2,18 +2,28 @@ import styled from '@emotion/styled';
import { CommentChip, CommentChipProps } from './CommentChip';
// TODO: tie those fixed values to the other components in the cell
const StyledCellWrapper = styled.div`
position: absolute;
right: -46px;
top: 3px;
`;
const StyledCommentChipContainer = styled.div`
position: relative;
right: 34px;
top: -13px;
width: 0;
height: 0;
right: 50px;
width: 50px;
display: flex;
justify-content: flex-end;
`;
export function CellCommentChip(props: CommentChipProps) {
return (
<StyledCellWrapper>
<CommentChip {...props} />
<StyledCommentChipContainer>
<CommentChip {...props} />
</StyledCommentChipContainer>
</StyledCellWrapper>
);
}

View File

@ -9,7 +9,7 @@ export type CommentChipProps = {
const StyledChip = styled.div`
height: 26px;
width: fit-content;
max-width: 42px;
padding-left: 4px;
padding-right: 4px;

View File

@ -37,6 +37,7 @@ const StyledThreadItemListContainer = styled.div`
max-height: 400px;
overflow: auto;
width: 100%;
gap: ${(props) => props.theme.spacing(4)};
`;
@ -46,6 +47,10 @@ export function CommentThread({ commentThread }: OwnProps) {
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.',
@ -53,35 +58,27 @@ export function CommentThread({ commentThread }: OwnProps) {
return;
}
if (!isNonEmptyString(commentText)) {
logError(
'In handleSendComment, trying to send empty text, this should not happen.',
);
return;
}
if (isDefined(currentUser)) {
createCommentMutation({
variables: {
commentId: v4(),
authorId: currentUser.id,
commentThreadId: commentThread.id,
commentText,
createdAt: new Date().toISOString(),
},
// TODO: find a way to have this configuration dynamic and typed
refetchQueries: [
'GetCommentThreadsByTargets',
'GetPeopleCommentsCount',
'GetCompanyCommentsCount',
],
onError: (error) => {
logError(
`In handleSendComment, createCommentMutation onError, error: ${error}`,
);
},
});
}
createCommentMutation({
variables: {
commentId: v4(),
authorId: currentUser.id,
commentThreadId: commentThread.id,
commentText,
createdAt: new Date().toISOString(),
},
// TODO: find a way to have this configuration dynamic and typed
// Also it cannot refetch queries than are not in the cache
refetchQueries: [
'GetCommentThreadsByTargets',
'GetPeopleCommentsCount',
'GetCompanyCommentsCount',
],
onError: (error) => {
logError(
`In handleSendComment, createCommentMutation onError, error: ${error}`,
);
},
});
}
return (
@ -91,7 +88,7 @@ export function CommentThread({ commentThread }: OwnProps) {
<CommentThreadItem key={comment.id} comment={comment} />
))}
</StyledThreadItemListContainer>
<AutosizeTextInput onSend={handleSendComment} />
<AutosizeTextInput onValidate={handleSendComment} />
</StyledContainer>
);
}

View File

@ -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>
);
}

View File

@ -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>
);
}

View File

@ -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');
};
}

View File

@ -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
}
}
}
}
`;

View File

@ -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
}
}
}
}
`;

View File

@ -0,0 +1,6 @@
import { atom } from 'recoil';
export const createdCommentThreadIdState = atom<string | null>({
key: 'comments/created-comment-thread-id',
default: null,
});

View File

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