Implemented comment thread target picker with new dropdown components (#295)

* First draft of new relation picker and usage in comments
---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Lucas Bordeau
2023-06-14 18:48:26 +02:00
committed by GitHub
parent 2a1804c153
commit fdfb6f10e2
22 changed files with 421 additions and 47 deletions

View File

@ -7,6 +7,7 @@
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.5",
"@floating-ui/react": "^0.24.3",
"@hello-pangea/dnd": "^16.2.0",
"@tabler/icons-react": "^2.20.0",
"@tanstack/react-table": "^8.8.5",

View File

@ -1151,14 +1151,14 @@ export type GetCommentThreadsByTargetsQueryVariables = Exact<{
}>;
export type GetCommentThreadsByTargetsQuery = { __typename?: 'Query', findManyCommentThreads: Array<{ __typename?: 'CommentThread', id: string, comments?: Array<{ __typename?: 'Comment', id: string, body: string, createdAt: string, updatedAt: string, author: { __typename?: 'User', id: string, displayName: string, 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, commentThreadTargets?: Array<{ __typename?: 'CommentThreadTarget', commentableId: string, commentableType: CommentableType }> | null }> };
export type GetCommentThreadQueryVariables = Exact<{
commentThreadId: Scalars['String'];
}>;
export type GetCommentThreadQuery = { __typename?: 'Query', findManyCommentThreads: Array<{ __typename?: 'CommentThread', id: string, comments?: Array<{ __typename?: 'Comment', id: string, body: string, createdAt: string, updatedAt: string, author: { __typename?: 'User', id: string, displayName: string, avatarUrl?: string | null } }> | null }> };
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, commentThreadTargets?: Array<{ __typename?: 'CommentThreadTarget', commentableId: string, commentableType: CommentableType }> | null }> };
export type GetCompaniesQueryVariables = Exact<{
orderBy?: InputMaybe<Array<CompanyOrderByWithRelationInput> | CompanyOrderByWithRelationInput>;
@ -1414,6 +1414,10 @@ export const GetCommentThreadsByTargetsDocument = gql`
avatarUrl
}
}
commentThreadTargets {
commentableId
commentableType
}
}
}
`;
@ -1461,6 +1465,10 @@ export const GetCommentThreadDocument = gql`
avatarUrl
}
}
commentThreadTargets {
commentableId
commentableType
}
}
}
`;

View File

@ -80,7 +80,7 @@ export function CommentHeader({ comment }: OwnProps) {
<Avatar
avatarUrl={avatarUrl}
size={16}
placeholderLetter={capitalizedFirstUsernameLetter}
placeholder={capitalizedFirstUsernameLetter}
/>
<StyledName>{authorName}</StyledName>
{showDate && (

View File

@ -11,6 +11,7 @@ import { isNonEmptyString } from '@/utils/type-guards/isNonEmptyString';
import { useCreateCommentMutation } from '~/generated/graphql';
import { CommentThreadItem } from './CommentThreadItem';
import { CommentThreadRelationPicker } from './CommentThreadRelationPicker';
type OwnProps = {
commentThread: CommentThreadForDrawer;
@ -88,6 +89,7 @@ export function CommentThread({ commentThread }: OwnProps) {
<CommentThreadItem key={comment.id} comment={comment} />
))}
</StyledThreadItemListContainer>
<CommentThreadRelationPicker commentThread={commentThread} />
<AutosizeTextInput onValidate={handleSendComment} />
</StyledContainer>
);

View File

@ -0,0 +1,206 @@
import { useState } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import {
autoUpdate,
flip,
offset,
shift,
size,
useFloating,
} from '@floating-ui/react';
import { debounce } from 'lodash';
import { CommentThreadForDrawer } from '@/comments/types/CommentThreadForDrawer';
import CompanyChip from '@/companies/components/CompanyChip';
import { DropdownMenu } from '@/ui/components/menu/DropdownMenu';
import { DropdownMenuCheckableItem } from '@/ui/components/menu/DropdownMenuCheckableItem';
import { DropdownMenuItem } from '@/ui/components/menu/DropdownMenuItem';
import { DropdownMenuItemContainer } from '@/ui/components/menu/DropdownMenuItemContainer';
import { DropdownMenuSearch } from '@/ui/components/menu/DropdownMenuSearch';
import { DropdownMenuSeparator } from '@/ui/components/menu/DropdownMenuSeparator';
import { IconArrowUpRight } from '@/ui/icons';
import { Avatar } from '@/users/components/Avatar';
import { getLogoUrlFromDomainName } from '@/utils/utils';
import { QueryMode, useSearchCompanyQueryQuery } from '~/generated/graphql';
type OwnProps = {
commentThread: CommentThreadForDrawer;
};
const StyledContainer = styled.div`
align-items: center;
display: flex;
flex-direction: row;
gap: ${(props) => props.theme.spacing(2)};
justify-content: flex-start;
width: 100%;
`;
const StyledRelationLabel = styled.div`
color: ${(props) => props.theme.text60};
display: flex;
flex-direction: row;
`;
const StyledRelationContainer = styled.div`
--horizontal-padding: ${(props) => props.theme.spacing(1)};
--vertical-padding: ${(props) => props.theme.spacing(1.5)};
border: 1px solid transparent;
cursor: pointer;
display: flex;
gap: ${(props) => props.theme.spacing(2)};
height: calc(32px - 2 * var(--vertical-padding));
&:hover {
background-color: ${(props) => props.theme.secondaryBackground};
border: 1px solid ${(props) => props.theme.lightBorder};
}
overflow: hidden;
padding: var(--vertical-padding) var(--horizontal-padding);
width: calc(100% - 2 * var(--horizontal-padding));
`;
// TODO: refactor icon button with new figma and merge
// const StyledAddButton = styled.div`
// align-items: center;
// background: ${(props) => props.theme.primaryBackgroundTransparent};
// border-radius: ${(props) => props.theme.borderRadius};
// box-shadow: ${(props) => props.theme.modalBoxShadow};
// cursor: pointer;
// display: flex;
// flex-direction: row;
// &:hover {
// background-color: ${(props) => props.theme.tertiaryBackground};
// }
// height: 20px;
// justify-content: center;
// width: 20px;
// `;
export function CommentThreadRelationPicker({ commentThread }: OwnProps) {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [searchFilter, setSearchFilter] = useState('');
const debouncedSetSearchFilter = debounce(setSearchFilter, 100, {
leading: true,
});
const { refs, floatingStyles } = useFloating({
strategy: 'fixed',
middleware: [offset(), flip(), shift(), size()],
whileElementsMounted: autoUpdate,
open: isMenuOpen,
});
const theme = useTheme();
const companyIds = commentThread.commentThreadTargets
?.filter((relation) => relation.commentableType === 'Company')
.map((relation) => relation.commentableId);
// const personIds = commentThread.commentThreadTargets
// ?.filter((relation) => relation.commentableType === 'Person')
// .map((relation) => relation.commentableId);
const { data: dataForChips } = useSearchCompanyQueryQuery({
variables: {
where: {
id: {
in: companyIds,
},
},
},
});
const { data: dataForSelect } = useSearchCompanyQueryQuery({
variables: {
where: {
name: {
contains: `%${searchFilter}%`,
mode: QueryMode.Insensitive,
},
},
limit: 5,
},
});
function handleFilterChange(event: React.ChangeEvent<HTMLInputElement>) {
debouncedSetSearchFilter(event.currentTarget.value);
}
function handleChangeRelationsClick() {
setIsMenuOpen((isOpen) => !isOpen);
}
const companiesForChips = dataForChips?.searchResults ?? [];
const companiesForSelect = dataForSelect?.searchResults ?? [];
return (
<StyledContainer>
<IconArrowUpRight size={20} color={theme.text40} />
<StyledRelationLabel>Relations</StyledRelationLabel>
<StyledRelationContainer
ref={refs.setReference}
onClick={handleChangeRelationsClick}
>
{companiesForChips?.map((company) => (
<CompanyChip
key={company.id}
name={company.name}
picture={getLogoUrlFromDomainName(company.domainName)}
/>
))}
</StyledRelationContainer>
{/* <StyledAddButton id="add-button" onClick={handleAddButtonClick}>
<IconPlus size={14} color={theme.text40} strokeWidth={1.5} />
</StyledAddButton> */}
{isMenuOpen && (
<DropdownMenu ref={refs.setFloating} style={floatingStyles}>
<DropdownMenuSearch
value={searchFilter}
onChange={handleFilterChange}
/>
<DropdownMenuSeparator />
<DropdownMenuItemContainer>
{companiesForSelect?.slice(0, 5)?.map((company) => (
<DropdownMenuCheckableItem
checked={
companiesForChips
?.map((companyForChip) => companyForChip.id)
?.includes(company.id) ?? false
}
onChange={(newCheckedValue) => {
if (newCheckedValue) {
}
}}
>
<Avatar
avatarUrl={getLogoUrlFromDomainName(company.domainName)}
placeholder={company.name}
size={16}
/>
{company.name}
</DropdownMenuCheckableItem>
))}
{companiesForSelect?.length === 0 && (
<DropdownMenuItem>No result</DropdownMenuItem>
)}
</DropdownMenuItemContainer>
</DropdownMenu>
)}
</StyledContainer>
);
}

View File

@ -0,0 +1,31 @@
import styled from '@emotion/styled';
import type { Meta, StoryObj } from '@storybook/react';
import { graphqlMocks } from '~/testing/graphqlMocks';
import { mockedCommentThreads } from '~/testing/mock-data/comment-threads';
import { getRenderWrapperForComponent } from '~/testing/renderWrappers';
import { CommentThreadRelationPicker } from '../CommentThreadRelationPicker';
const meta: Meta<typeof CommentThreadRelationPicker> = {
title: 'Comments/CommentThreadRelationPicker',
component: CommentThreadRelationPicker,
parameters: {
msw: graphqlMocks,
},
};
const StyledContainer = styled.div`
width: 400px;
`;
export default meta;
type Story = StoryObj<typeof CommentThreadRelationPicker>;
export const Default: Story = {
render: getRenderWrapperForComponent(
<StyledContainer>
<CommentThreadRelationPicker commentThread={mockedCommentThreads[0]} />
</StyledContainer>,
),
};

View File

@ -25,6 +25,10 @@ export const GET_COMMENT_THREADS_BY_TARGETS = gql`
avatarUrl
}
}
commentThreadTargets {
commentableId
commentableType
}
}
}
`;
@ -44,6 +48,10 @@ export const GET_COMMENT_THREAD = gql`
avatarUrl
}
}
commentThreadTargets {
commentableId
commentableType
}
}
}
`;

View File

@ -1,6 +1,7 @@
import * as React from 'react';
import styled from '@emotion/styled';
import { Avatar } from '@/users/components/Avatar';
export type CompanyChipPropsType = {
name: string;
picture?: string;
@ -15,6 +16,8 @@ const StyledContainer = styled.span`
gap: ${(props) => props.theme.spacing(1)};
padding: ${(props) => props.theme.spacing(1)};
user-select: none;
:hover {
filter: brightness(95%);
}
@ -30,10 +33,11 @@ function CompanyChip({ name, picture }: CompanyChipPropsType) {
return (
<StyledContainer data-testid="company-chip">
{picture && (
<img
data-testid="company-chip-image"
src={picture?.toString()}
alt={`${name}-company-logo`}
<Avatar
avatarUrl={picture?.toString()}
placeholder={name}
type="squared"
size={14}
/>
)}
{name}

View File

@ -2,8 +2,8 @@ import * as React from 'react';
import styled from '@emotion/styled';
type OwnProps = {
name: string;
id: string;
name?: string;
id?: string;
checked?: boolean;
indeterminate?: boolean;
onChange?: (newCheckedValue: boolean) => void;

View File

@ -17,4 +17,6 @@ export const DropdownMenu = styled.div`
height: fit-content;
width: 200px;
z-index: ${(props) => props.theme.lastLayerZIndex};
`;

View File

@ -8,7 +8,7 @@ import { DropdownMenuButton } from './DropdownMenuButton';
type Props = {
checked: boolean;
onChange?: (newCheckedValue: boolean) => void;
id: string;
id?: string;
};
const DropdownMenuCheckableItemContainer = styled(DropdownMenuButton)`

View File

@ -25,8 +25,10 @@ type Story = StoryObj<typeof DropdownMenu>;
const FakeContentBelow = () => (
<div style={{ position: 'absolute' }}>
askjdlaksjdlaksjdlakjsdlkj lkajsldkjalskd jalksdj alksjd alskjd alksjd
alksjd laksjd askjdlaksjdlaksjdlakjsdlkj lkajsldkjalskd jalksdj alksjd
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat.
</div>
);
@ -217,7 +219,7 @@ const FakeSelectableMenuItemWithAvatarList = () => {
onClick={() => setSelectedItem(item.id)}
>
<Avatar
placeholderLetter="A"
placeholder="A"
avatarUrl={item.avatarUrl}
size={16}
type="squared"
@ -303,7 +305,7 @@ const FakeCheckableMenuItemWithAvatarList = () => {
}}
>
<Avatar
placeholderLetter="A"
placeholder="A"
avatarUrl={item.avatarUrl}
size={16}
type="squared"

View File

@ -0,0 +1,3 @@
export function SelectSingleEntity() {
return <></>;
}

View File

@ -26,3 +26,4 @@ export { IconChevronDown } from '@tabler/icons-react';
export { IconArrowNarrowDown } from '@tabler/icons-react';
export { IconArrowNarrowUp } from '@tabler/icons-react';
export { IconArrowRight } from '@tabler/icons-react';
export { IconArrowUpRight } from '@tabler/icons-react';

View File

@ -54,7 +54,7 @@ const lightThemeSpecific = {
lighterBackgroundTransparent: 'rgba(0, 0, 0, 0.02)',
primaryBorder: 'rgba(0, 0, 0, 0.08)',
lightBorder: '#f5f5f5',
lightBorder: 'rgba(245, 245, 245, 1)',
mediumBorder: '#ebebeb',
clickableElementBackgroundTransition: 'background 0.1s ease',

View File

@ -5,26 +5,30 @@ import { isNonEmptyString } from '@/utils/type-guards/isNonEmptyString';
type OwnProps = {
avatarUrl: string | null | undefined;
size: number;
placeholderLetter: string;
placeholder: string;
type?: 'squared' | 'rounded';
};
export const StyledAvatar = styled.div<Omit<OwnProps, 'placeholderLetter'>>`
align-items: center;
export const StyledAvatar = styled.div<Omit<OwnProps, 'placeholder'>>`
background-color: ${(props) =>
!isNonEmptyString(props.avatarUrl)
? props.theme.tertiaryBackground
: 'none'};
background-image: url(${(props) =>
isNonEmptyString(props.avatarUrl) ? props.avatarUrl : 'none'});
background-image: url(${(props) =>
isNonEmptyString(props.avatarUrl) ? props.avatarUrl : 'none'});
background-position: center center;
background-repeat: no-repeat;
background-size: cover;
border-radius: ${(props) => (props.type === 'rounded' ? '50%' : '2px')};
border-radius: ${(props) => (props.type === 'rounded' ? '50%' : '2px')};
display: flex;
height: ${(props) => props.size}px;
height: ${(props) => props.size}px;
justify-content: center;
width: ${(props) => props.size}px;
width: ${(props) => props.size}px;
`;
@ -50,7 +54,7 @@ export const StyledPlaceholderLetter = styled.div<StyledPlaceholderLetterProps>`
export function Avatar({
avatarUrl,
size,
placeholderLetter,
placeholder,
type = 'squared',
}: OwnProps) {
const noAvatarUrl = !isNonEmptyString(avatarUrl);
@ -59,7 +63,7 @@ export function Avatar({
<StyledAvatar avatarUrl={avatarUrl} size={size} type={type}>
{noAvatarUrl && (
<StyledPlaceholderLetter size={size}>
{placeholderLetter}
{placeholder[0]?.toLocaleUpperCase()}
</StyledPlaceholderLetter>
)}
</StyledAvatar>

View File

@ -17,34 +17,24 @@ const avatarUrl =
export const Rounded: Story = {
render: getRenderWrapperForComponent(
<Avatar
avatarUrl={avatarUrl}
size={16}
placeholderLetter="L"
type="rounded"
/>,
<Avatar avatarUrl={avatarUrl} size={16} placeholder="L" type="rounded" />,
),
};
export const Squared: Story = {
render: getRenderWrapperForComponent(
<Avatar
avatarUrl={avatarUrl}
size={16}
placeholderLetter="L"
type="squared"
/>,
<Avatar avatarUrl={avatarUrl} size={16} placeholder="L" type="squared" />,
),
};
export const NoAvatarPictureRounded: Story = {
render: getRenderWrapperForComponent(
<Avatar avatarUrl={''} size={16} placeholderLetter="L" type="rounded" />,
<Avatar avatarUrl={''} size={16} placeholder="L" type="rounded" />,
),
};
export const NoAvatarPictureSquared: Story = {
render: getRenderWrapperForComponent(
<Avatar avatarUrl={''} size={16} placeholderLetter="L" type="squared" />,
<Avatar avatarUrl={''} size={16} placeholder="L" type="squared" />,
),
};

View File

@ -0,0 +1,76 @@
import { CommentableType, CommentThread } from '~/generated/graphql';
export const mockedCommentThreads: Array<CommentThread> = [
{
id: '89bb825c-171e-4bcc-9cf7-43448d6fb230',
createdAt: '2023-04-26T10:12:42.33625+00:00',
updatedAt: '2023-04-26T10:23:42.33625+00:00',
commentThreadTargets: [
{
id: '89bb825c-171e-4bcc-9cf7-43448d6fb300',
createdAt: '2023-04-26T10:12:42.33625+00:00',
updatedAt: '2023-04-26T10:23:42.33625+00:00',
commentableType: CommentableType.Company,
commentableId: '89bb825c-171e-4bcc-9cf7-43448d6fb278', // airbnb
commentThreadId: '89bb825c-171e-4bcc-9cf7-43448d6fb230',
commentThread: {
id: '89bb825c-171e-4bcc-9cf7-43448d6fb230',
createdAt: '2023-04-26T10:12:42.33625+00:00',
updatedAt: '2023-04-26T10:23:42.33625+00:00',
},
__typename: 'CommentThreadTarget',
},
{
id: '89bb825c-171e-4bcc-9cf7-43448d6fb301',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
commentableType: CommentableType.Company,
commentableId: 'b396e6b9-dc5c-4643-bcff-61b6cf7523ae', // aircall
commentThreadId: '89bb825c-171e-4bcc-9cf7-43448d6fb231',
commentThread: {
id: '89bb825c-171e-4bcc-9cf7-43448d6fb231',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
__typename: 'CommentThreadTarget',
},
],
__typename: 'CommentThread',
},
{
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
commentThreadTargets: [
{
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
createdAt: '2023-04-26T10:12:42.33625+00:00',
updatedAt: '2023-04-26T10:23:42.33625+00:00',
commentableType: CommentableType.Person,
commentableId: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6b', // Alexandre
commentThreadId: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
commentThread: {
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
createdAt: '2023-04-26T10:12:42.33625+00:00',
updatedAt: '2023-04-26T10:23:42.33625+00:00',
},
__typename: 'CommentThreadTarget',
},
{
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
commentableType: CommentableType.Person,
commentableId: '7dfbc3f7-6e5e-4128-957e-8d86808cdf6d', // Jean d'Eau
commentThreadId: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
commentThread: {
id: '89bb825c-171e-4bcc-9cf7-43448d6fb278',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
},
__typename: 'CommentThreadTarget',
},
],
__typename: 'CommentThread',
},
];

View File

@ -52,6 +52,11 @@ function filterData<DataT>(
filterElement.contains.replaceAll('%', '').toLocaleLowerCase(),
);
}
if (filterElement.in) {
const itemValue = item[key as keyof typeof item] as string;
return filterElement.in.includes(itemValue);
}
}
return false;
});

View File

@ -1881,13 +1881,29 @@
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.3.0.tgz#113bc85fa102cf890ae801668f43ee265c547a09"
integrity sha512-vX1WVAdPjZg9DkDkC+zEx/tKtnST6/qcNpwcjeBgco3XRNHz5PUA+ivi/yr6G3o0kMR60uKBJcfOdfzOFI7PMQ==
"@floating-ui/dom@^1.0.0":
"@floating-ui/dom@^1.0.0", "@floating-ui/dom@^1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.3.0.tgz#69456f2164fc3d33eb40837686eaf71537235ac9"
integrity sha512-qIAwejE3r6NeA107u4ELDKkH8+VtgRKdXqtSPaKflL2S2V+doyN+Wt9s5oHKXPDo4E8TaVXaHT3+6BbagH31xw==
dependencies:
"@floating-ui/core" "^1.3.0"
"@floating-ui/react-dom@^2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.1.tgz#7972a4fc488a8c746cded3cfe603b6057c308a91"
integrity sha512-rZtAmSht4Lry6gdhAJDrCp/6rKN7++JnL1/Anbr/DdeyYXQPxvg/ivrbYvJulbRf4vL8b212suwMM2lxbv+RQA==
dependencies:
"@floating-ui/dom" "^1.3.0"
"@floating-ui/react@^0.24.3":
version "0.24.3"
resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.24.3.tgz#4f11f09c7245555724f5167dd6925133457db89c"
integrity sha512-wWC9duiog4HmbgKSKObDRuXqMjZR/6m75MIG+slm5CVWbridAjK9STcnCsGYmdpK78H/GmzYj4ADVP8paZVLYQ==
dependencies:
"@floating-ui/react-dom" "^2.0.1"
aria-hidden "^1.1.3"
tabbable "^6.0.1"
"@graphql-codegen/cli@^3.3.1":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@graphql-codegen/cli/-/cli-3.3.1.tgz#103e7a2263126fdde168a1ce623fc2bdc05352f0"
@ -5437,7 +5453,7 @@ argparse@^2.0.1:
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
aria-hidden@^1.1.1:
aria-hidden@^1.1.1, aria-hidden@^1.1.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.3.tgz#14aeb7fb692bbb72d69bebfa47279c1fd725e954"
integrity sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==
@ -15557,6 +15573,11 @@ synchronous-promise@^2.0.15:
resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.17.tgz#38901319632f946c982152586f2caf8ddc25c032"
integrity sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g==
tabbable@^6.0.1:
version "6.1.2"
resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.1.2.tgz#b0d3ca81d582d48a80f71b267d1434b1469a3703"
integrity sha512-qCN98uP7i9z0fIS4amQ5zbGBOq+OSigYeGvPy7NDk8Y9yncqDZ9pRPgfsc2PJIVM9RrJj7GIfuRgmjoUU9zTHQ==
tailwindcss@^3.0.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.2.tgz#2f9e35d715fdf0bbf674d90147a0684d7054a2d3"

View File

@ -65,15 +65,6 @@ export class CommentThreadResolver {
}
return createdCommentThread;
// return this.prismaService.commentThread.create({
// data: {
// ...args.data,
// ...{ commentThreadTargets: undefined },
// ...{ comments: { createMany: { data: newCommentData } } },
// ...{ workspace: { connect: { id: workspace.id } } },
// },
// });
}
@Query(() => [CommentThread])
@ -86,6 +77,7 @@ export class CommentThreadResolver {
args,
workspace,
);
const result = await this.prismaService.commentThread.findMany(
preparedArgs,
);

View File

@ -1,4 +1,5 @@
import * as TypeGraphQL from '@nestjs/graphql';
import { CommentThreadTarget } from 'src/api/@generated/comment-thread-target/comment-thread-target.model';
import { CommentThread } from 'src/api/@generated/comment-thread/comment-thread.model';
import { Comment } from 'src/api/@generated/comment/comment.model';
import { PrismaService } from 'src/database/prisma.service';
@ -23,4 +24,21 @@ export class CommentThreadRelationsResolver {
},
});
}
@TypeGraphQL.ResolveField(() => [CommentThreadTarget], {
nullable: true,
})
async commentThreadTargets(
@TypeGraphQL.Root() commentThread: CommentThread,
): Promise<CommentThreadTarget[]> {
return this.prismaService.commentThreadTarget.findMany({
where: {
commentThreadId: commentThread.id,
},
orderBy: {
// TODO: find a way to pass it in the query
createdAt: 'desc',
},
});
}
}