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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 574 additions and 86 deletions

View File

@ -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 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<{
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 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<{
orderBy?: InputMaybe<Array<CompanyOrderByWithRelationInput> | CompanyOrderByWithRelationInput>;
where?: InputMaybe<CompanyWhereInput>;
@ -1285,6 +1304,65 @@ 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!]!) {
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`
query GetCompanyCommentsCount($where: CompanyWhereInput) {
companies: findManyCompany(where: $where) {
@ -1403,6 +1481,52 @@ export function useGetCommentThreadsByTargetsLazyQuery(baseOptions?: Apollo.Lazy
export type GetCommentThreadsByTargetsQueryHookResult = ReturnType<typeof useGetCommentThreadsByTargetsQuery>;
export type GetCommentThreadsByTargetsLazyQueryHookResult = ReturnType<typeof useGetCommentThreadsByTargetsLazyQuery>;
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`
query GetCompanies($orderBy: [CompanyOrderByWithRelationInput!], $where: CompanyWhereInput) {
companies: findManyCompany(orderBy: $orderBy, where: $where) {

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

View File

@ -3,6 +3,7 @@ import { useOpenCommentRightDrawer } from '@/comments/hooks/useOpenCommentRightD
import { useCompanyCommentsCountQuery } from '@/comments/services';
import EditableChip from '@/ui/components/editable-cell/types/EditableChip';
import { getLogoUrlFromDomainName } from '@/utils/utils';
import { CommentableType } from '~/generated/graphql';
import { Company } from '../interfaces/company.interface';
import { updateCompany } from '../services';
@ -22,7 +23,7 @@ export function CompanyEditableNameChipCell({ company }: OwnProps) {
openCommentRightDrawer([
{
type: 'Company',
type: CommentableType.Company,
id: company.id,
},
]);

View File

@ -4,6 +4,7 @@ import styled from '@emotion/styled';
import { CellCommentChip } from '@/comments/components/comments/CellCommentChip';
import { useOpenCommentRightDrawer } from '@/comments/hooks/useOpenCommentRightDrawer';
import { EditableDoubleText } from '@/ui/components/editable-cell/types/EditableDoubleText';
import { CommentableType } from '~/generated/graphql';
import { usePeopleCommentsCountQuery } from '../../comments/services';
@ -49,7 +50,7 @@ export function EditablePeopleFullName({
openCommentRightDrawer([
{
type: 'Person',
type: CommentableType.Person,
id: personId,
},
]);

View File

@ -11,6 +11,7 @@ import {
import { SearchConfigType } from '@/search/interfaces/interface';
import { SEARCH_COMPANY_QUERY } from '@/search/services/search';
import { EditableRelation } from '@/ui/components/editable-cell/types/EditableRelation';
import { logError } from '@/utils/logs/logError';
import { getLogoUrlFromDomainName } from '@/utils/utils';
import {
QueryMode,
@ -57,7 +58,7 @@ export function PeopleCompanyCell({ people }: OwnProps) {
});
} catch (error) {
// TODO: handle error better
console.log(error);
logError(error);
}
setIsCreating(false);

View File

@ -5,16 +5,19 @@ import { HiArrowSmRight } from 'react-icons/hi';
import TextareaAutosize from 'react-textarea-autosize';
import styled from '@emotion/styled';
import { IconButton } from '../buttons/IconButton';
import { IconButton } from '@/ui/components/buttons/IconButton';
const MAX_ROWS = 5;
type OwnProps = {
onSend?: (text: string) => void;
onValidate?: (text: string) => void;
minRows?: number;
placeholder?: string;
};
const StyledContainer = styled.div`
display: flex;
min-height: 32px;
width: 100%;
`;
@ -43,14 +46,21 @@ const StyledTextArea = styled(TextareaAutosize)`
}
`;
// TODO: this messes with the layout, fix it
const StyledBottomRightIconButton = styled.div`
width: 0px;
position: relative;
top: calc(100% - 26.5px);
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 isSendButtonDisabled = !text;
@ -58,12 +68,12 @@ export function AutosizeTextInput({ placeholder, onSend }: OwnProps) {
useHotkeys(
['shift+enter', 'enter'],
(event: KeyboardEvent, handler: HotkeysEvent) => {
if (handler.shift) {
if (handler.shift || !isFocused) {
return;
} else {
event.preventDefault();
onSend?.(text);
onValidate?.(text);
setText('');
}
@ -72,12 +82,16 @@ export function AutosizeTextInput({ placeholder, onSend }: OwnProps) {
enableOnContentEditable: true,
enableOnFormTags: true,
},
[onSend, text, setText],
[onValidate, text, setText, isFocused],
);
useHotkeys(
'esc',
(event: KeyboardEvent) => {
if (!isFocused) {
return;
}
event.preventDefault();
setText('');
@ -86,7 +100,7 @@ export function AutosizeTextInput({ placeholder, onSend }: OwnProps) {
enableOnContentEditable: true,
enableOnFormTags: true,
},
[onSend, setText],
[onValidate, setText, isFocused],
);
function handleInputChange(event: React.FormEvent<HTMLTextAreaElement>) {
@ -96,19 +110,24 @@ export function AutosizeTextInput({ placeholder, onSend }: OwnProps) {
}
function handleOnClickSendButton() {
onSend?.(text);
onValidate?.(text);
setText('');
}
const computedMinRows = minRows > MAX_ROWS ? MAX_ROWS : minRows;
return (
<>
<StyledContainer>
<StyledTextArea
placeholder={placeholder || 'Write something...'}
maxRows={5}
maxRows={MAX_ROWS}
minRows={computedMinRows}
onChange={handleInputChange}
value={text}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
/>
<StyledBottomRightIconButton>
<IconButton

View File

@ -7,11 +7,6 @@ import { AutosizeTextInput } from '../AutosizeTextInput';
const meta: Meta<typeof AutosizeTextInput> = {
title: 'Components/Common/AutosizeTextInput',
component: AutosizeTextInput,
argTypes: {
onSend: {
action: 'onSend',
},
},
};
export default meta;

View File

@ -1,23 +1,17 @@
import { FaRegComment } from 'react-icons/fa';
import { useOpenRightDrawer } from '@/ui/layout/right-drawer/hooks/useOpenRightDrawer';
import { EntityTableActionBarButton } from './EntityTableActionBarButton';
export function TableActionBarButtonToggleComments() {
// TODO: here it would be nice to access the table context
// But let's see when we have custom entities and properties
const openRightDrawer = useOpenRightDrawer();
async function handleButtonClick() {
openRightDrawer('comments');
}
type OwnProps = {
onClick: () => void;
};
export function TableActionBarButtonToggleComments({ onClick }: OwnProps) {
return (
<EntityTableActionBarButton
label="Comment"
icon={<FaRegComment size={16} />}
onClick={handleButtonClick}
onClick={onClick}
/>
);
}

View File

@ -5,7 +5,7 @@ import { useRecoilState } from 'recoil';
import { Panel } from '../Panel';
import { RightDrawer } from '../right-drawer/components/RightDrawer';
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 = {
children: JSX.Element;
@ -20,13 +20,14 @@ const StyledContainer = styled.div`
width: 100%;
`;
const TOPBAR_HEIGHT = '48px';
const MainContainer = styled.div`
display: flex;
flex-direction: row;
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};
padding-right: ${(props) => props.theme.spacing(3)};
padding-bottom: ${(props) => props.theme.spacing(3)};

View File

@ -3,4 +3,5 @@ import styled from '@emotion/styled';
export const RightDrawerBody = styled.div`
display: flex;
flex-direction: column;
overflow: auto;
`;

View File

@ -1,6 +1,7 @@
import { useRecoilState } from 'recoil';
import { RightDrawerComments } from '@/comments/components/comments/RightDrawerComments';
import { RightDrawerCreateCommentThread } from '@/comments/components/comments/RightDrawerCreateCommentThread';
import { isDefined } from '@/utils/type-guards/isDefined';
import { rightDrawerPageState } from '../states/rightDrawerPageState';
@ -12,5 +13,11 @@ export function RightDrawerRouter() {
return <></>;
}
return rightDrawerPage === 'comments' ? <RightDrawerComments /> : <></>;
return rightDrawerPage === 'comments' ? (
<RightDrawerComments />
) : rightDrawerPage === 'create-comment-thread' ? (
<RightDrawerCreateCommentThread />
) : (
<></>
);
}

View File

@ -5,7 +5,7 @@ import { RightDrawerTopBarCloseButton } from './RightDrawerTopBarCloseButton';
const StyledRightDrawerTopBar = styled.div`
display: flex;
flex-direction: row;
height: 40px;
min-height: 40px;
align-items: center;
justify-content: space-between;
padding-left: 8px;

View File

@ -4,5 +4,5 @@ import { RightDrawerPage } from '../types/RightDrawerPage';
export const rightDrawerPageState = atom<RightDrawerPage | null>({
key: 'ui/layout/right-drawer-page',
default: 'comments',
default: null,
});

View File

@ -1 +1 @@
export type RightDrawerPage = 'comments';
export type RightDrawerPage = 'comments' | 'create-comment-thread';

View File

@ -2,13 +2,15 @@ import { ReactNode } from 'react';
import { TbPlus } from 'react-icons/tb';
import styled from '@emotion/styled';
export const TOP_BAR_MIN_HEIGHT = '40px';
const TopBarContainer = styled.div`
display: flex;
flex-direction: row;
height: 38px;
min-height: ${TOP_BAR_MIN_HEIGHT};
align-items: center;
background: ${(props) => props.theme.noisyBackground};
padding: 8px;
padding: ${(props) => props.theme.spacing(2)};
font-size: 14px;
color: ${(props) => props.theme.text80};
`;

View File

@ -20,12 +20,12 @@ import {
} from '@/filters-and-sorts/helpers';
import { SelectedFilterType } from '@/filters-and-sorts/interfaces/filters/interface';
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 { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer';
import { BoolExpType } from '@/utils/interfaces/generic.interface';
import { CompanyOrderByWithRelationInput as Companies_Order_By } from '~/generated/graphql';
import { TableActionBarButtonCreateCommentThreadCompany } from './table/TableActionBarButtonCreateCommentThreadCompany';
import { TableActionBarButtonDeleteCompanies } from './table/TableActionBarButtonDeleteCompanies';
import { useCompaniesColumns } from './companies-columns';
import { availableFilters } from './companies-filters';
@ -93,7 +93,7 @@ export function Companies() {
/>
</StyledCompaniesContainer>
<EntityTableActionBar>
<TableActionBarButtonToggleComments />
<TableActionBarButtonCreateCommentThreadCompany />
<TableActionBarButtonDeleteCompanies />
</EntityTableActionBar>
</>

View File

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

View File

@ -17,11 +17,11 @@ import {
usePeopleQuery,
} from '@/people/services';
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 { WithTopBarContainer } from '@/ui/layout/containers/WithTopBarContainer';
import { BoolExpType } from '@/utils/interfaces/generic.interface';
import { TableActionBarButtonCreateCommentThreadPeople } from './table/TableActionBarButtonCreateCommentThreadPeople';
import { TableActionBarButtonDeletePeople } from './table/TableActionBarButtonDeletePeople';
import { usePeopleColumns } from './people-columns';
import { availableFilters } from './people-filters';
@ -91,7 +91,7 @@ export function People() {
/>
</StyledPeopleContainer>
<EntityTableActionBar>
<TableActionBarButtonToggleComments />
<TableActionBarButtonCreateCommentThreadPeople />
<TableActionBarButtonDeletePeople />
</EntityTableActionBar>
</>

View File

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

View File

@ -32,13 +32,48 @@ export class CommentThreadResolver {
...{ workspaceId: workspace.id },
}))
: [];
return this.prismaService.commentThread.create({
const createdCommentThread = await this.prismaService.commentThread.create({
data: {
...args.data,
...{ commentThreadTargets: undefined },
...{ comments: { createMany: { data: newCommentData } } },
...{ 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])

View File

@ -14,12 +14,13 @@ export class CreateOneCommentThreadGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const gqlContext = GqlExecutionContext.create(context);
// TODO: type request
const request = gqlContext.getContext().req;
const args = gqlContext.getArgs();
const targets = args.data?.commentThreadTargets?.createMany?.data;
const comments = args.data?.comments?.createMany?.data;
const workspaceId = await request.workspace;
const workspace = await request.workspace;
if (!targets || targets.length === 0) {
throw new HttpException(
@ -52,7 +53,7 @@ export class CreateOneCommentThreadGuard implements CanActivate {
where: { id: target.commentableId },
});
if (!targetEntity || targetEntity.workspaceId !== workspaceId) {
if (!targetEntity || targetEntity.workspaceId !== workspace.id) {
throw new HttpException(
{ reason: 'CommentThreadTarget not found' },
HttpStatus.NOT_FOUND,
@ -90,10 +91,10 @@ export class CreateOneCommentThreadGuard implements CanActivate {
if (
!userWorkspaceMember ||
userWorkspaceMember.workspaceId !== workspaceId
userWorkspaceMember.workspaceId !== workspace.id
) {
throw new HttpException(
{ reason: 'Comment.authorId not found' },
{ reason: 'userWorkspaceMember.workspaceId not found' },
HttpStatus.NOT_FOUND,
);
}

View File

@ -18,7 +18,6 @@ export class UpdateOneGuard implements CanActivate {
const entity = gqlContext.getArgByIndex(3).returnType?.name;
const args = gqlContext.getArgs();
console.log(args.data);
if (!entity || !args.where?.id) {
throw new HttpException(
{ reason: 'Invalid Request' },