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", "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@emotion/react": "^11.10.6", "@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.5", "@emotion/styled": "^11.10.5",
"@floating-ui/react": "^0.24.3",
"@hello-pangea/dnd": "^16.2.0", "@hello-pangea/dnd": "^16.2.0",
"@tabler/icons-react": "^2.20.0", "@tabler/icons-react": "^2.20.0",
"@tanstack/react-table": "^8.8.5", "@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<{ export type GetCommentThreadQueryVariables = Exact<{
commentThreadId: Scalars['String']; 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<{ export type GetCompaniesQueryVariables = Exact<{
orderBy?: InputMaybe<Array<CompanyOrderByWithRelationInput> | CompanyOrderByWithRelationInput>; orderBy?: InputMaybe<Array<CompanyOrderByWithRelationInput> | CompanyOrderByWithRelationInput>;
@ -1414,6 +1414,10 @@ export const GetCommentThreadsByTargetsDocument = gql`
avatarUrl avatarUrl
} }
} }
commentThreadTargets {
commentableId
commentableType
}
} }
} }
`; `;
@ -1461,6 +1465,10 @@ export const GetCommentThreadDocument = gql`
avatarUrl avatarUrl
} }
} }
commentThreadTargets {
commentableId
commentableType
}
} }
} }
`; `;

View File

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

View File

@ -11,6 +11,7 @@ import { isNonEmptyString } from '@/utils/type-guards/isNonEmptyString';
import { useCreateCommentMutation } from '~/generated/graphql'; import { useCreateCommentMutation } from '~/generated/graphql';
import { CommentThreadItem } from './CommentThreadItem'; import { CommentThreadItem } from './CommentThreadItem';
import { CommentThreadRelationPicker } from './CommentThreadRelationPicker';
type OwnProps = { type OwnProps = {
commentThread: CommentThreadForDrawer; commentThread: CommentThreadForDrawer;
@ -88,6 +89,7 @@ export function CommentThread({ commentThread }: OwnProps) {
<CommentThreadItem key={comment.id} comment={comment} /> <CommentThreadItem key={comment.id} comment={comment} />
))} ))}
</StyledThreadItemListContainer> </StyledThreadItemListContainer>
<CommentThreadRelationPicker commentThread={commentThread} />
<AutosizeTextInput onValidate={handleSendComment} /> <AutosizeTextInput onValidate={handleSendComment} />
</StyledContainer> </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 avatarUrl
} }
} }
commentThreadTargets {
commentableId
commentableType
}
} }
} }
`; `;
@ -44,6 +48,10 @@ export const GET_COMMENT_THREAD = gql`
avatarUrl avatarUrl
} }
} }
commentThreadTargets {
commentableId
commentableType
}
} }
} }
`; `;

View File

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

View File

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

View File

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

View File

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

View File

@ -25,8 +25,10 @@ type Story = StoryObj<typeof DropdownMenu>;
const FakeContentBelow = () => ( const FakeContentBelow = () => (
<div style={{ position: 'absolute' }}> <div style={{ position: 'absolute' }}>
askjdlaksjdlaksjdlakjsdlkj lkajsldkjalskd jalksdj alksjd alskjd alksjd Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
alksjd laksjd askjdlaksjdlaksjdlakjsdlkj lkajsldkjalskd jalksdj alksjd 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> </div>
); );
@ -217,7 +219,7 @@ const FakeSelectableMenuItemWithAvatarList = () => {
onClick={() => setSelectedItem(item.id)} onClick={() => setSelectedItem(item.id)}
> >
<Avatar <Avatar
placeholderLetter="A" placeholder="A"
avatarUrl={item.avatarUrl} avatarUrl={item.avatarUrl}
size={16} size={16}
type="squared" type="squared"
@ -303,7 +305,7 @@ const FakeCheckableMenuItemWithAvatarList = () => {
}} }}
> >
<Avatar <Avatar
placeholderLetter="A" placeholder="A"
avatarUrl={item.avatarUrl} avatarUrl={item.avatarUrl}
size={16} size={16}
type="squared" 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 { IconArrowNarrowDown } from '@tabler/icons-react';
export { IconArrowNarrowUp } from '@tabler/icons-react'; export { IconArrowNarrowUp } from '@tabler/icons-react';
export { IconArrowRight } 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)', lighterBackgroundTransparent: 'rgba(0, 0, 0, 0.02)',
primaryBorder: 'rgba(0, 0, 0, 0.08)', primaryBorder: 'rgba(0, 0, 0, 0.08)',
lightBorder: '#f5f5f5', lightBorder: 'rgba(245, 245, 245, 1)',
mediumBorder: '#ebebeb', mediumBorder: '#ebebeb',
clickableElementBackgroundTransition: 'background 0.1s ease', clickableElementBackgroundTransition: 'background 0.1s ease',

View File

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

View File

@ -17,34 +17,24 @@ const avatarUrl =
export const Rounded: Story = { export const Rounded: Story = {
render: getRenderWrapperForComponent( render: getRenderWrapperForComponent(
<Avatar <Avatar avatarUrl={avatarUrl} size={16} placeholder="L" type="rounded" />,
avatarUrl={avatarUrl}
size={16}
placeholderLetter="L"
type="rounded"
/>,
), ),
}; };
export const Squared: Story = { export const Squared: Story = {
render: getRenderWrapperForComponent( render: getRenderWrapperForComponent(
<Avatar <Avatar avatarUrl={avatarUrl} size={16} placeholder="L" type="squared" />,
avatarUrl={avatarUrl}
size={16}
placeholderLetter="L"
type="squared"
/>,
), ),
}; };
export const NoAvatarPictureRounded: Story = { export const NoAvatarPictureRounded: Story = {
render: getRenderWrapperForComponent( render: getRenderWrapperForComponent(
<Avatar avatarUrl={''} size={16} placeholderLetter="L" type="rounded" />, <Avatar avatarUrl={''} size={16} placeholder="L" type="rounded" />,
), ),
}; };
export const NoAvatarPictureSquared: Story = { export const NoAvatarPictureSquared: Story = {
render: getRenderWrapperForComponent( 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(), filterElement.contains.replaceAll('%', '').toLocaleLowerCase(),
); );
} }
if (filterElement.in) {
const itemValue = item[key as keyof typeof item] as string;
return filterElement.in.includes(itemValue);
}
} }
return false; return false;
}); });

View File

@ -1881,13 +1881,29 @@
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.3.0.tgz#113bc85fa102cf890ae801668f43ee265c547a09" resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.3.0.tgz#113bc85fa102cf890ae801668f43ee265c547a09"
integrity sha512-vX1WVAdPjZg9DkDkC+zEx/tKtnST6/qcNpwcjeBgco3XRNHz5PUA+ivi/yr6G3o0kMR60uKBJcfOdfzOFI7PMQ== 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" version "1.3.0"
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.3.0.tgz#69456f2164fc3d33eb40837686eaf71537235ac9" resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.3.0.tgz#69456f2164fc3d33eb40837686eaf71537235ac9"
integrity sha512-qIAwejE3r6NeA107u4ELDKkH8+VtgRKdXqtSPaKflL2S2V+doyN+Wt9s5oHKXPDo4E8TaVXaHT3+6BbagH31xw== integrity sha512-qIAwejE3r6NeA107u4ELDKkH8+VtgRKdXqtSPaKflL2S2V+doyN+Wt9s5oHKXPDo4E8TaVXaHT3+6BbagH31xw==
dependencies: dependencies:
"@floating-ui/core" "^1.3.0" "@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": "@graphql-codegen/cli@^3.3.1":
version "3.3.1" version "3.3.1"
resolved "https://registry.yarnpkg.com/@graphql-codegen/cli/-/cli-3.3.1.tgz#103e7a2263126fdde168a1ce623fc2bdc05352f0" 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" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== 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" version "1.2.3"
resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.3.tgz#14aeb7fb692bbb72d69bebfa47279c1fd725e954" resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.3.tgz#14aeb7fb692bbb72d69bebfa47279c1fd725e954"
integrity sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ== 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" resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.17.tgz#38901319632f946c982152586f2caf8ddc25c032"
integrity sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g== 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: tailwindcss@^3.0.2:
version "3.3.2" version "3.3.2"
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.2.tgz#2f9e35d715fdf0bbf674d90147a0684d7054a2d3" resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.2.tgz#2f9e35d715fdf0bbf674d90147a0684d7054a2d3"

View File

@ -65,15 +65,6 @@ export class CommentThreadResolver {
} }
return createdCommentThread; 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])
@ -86,6 +77,7 @@ export class CommentThreadResolver {
args, args,
workspace, workspace,
); );
const result = await this.prismaService.commentThread.findMany( const result = await this.prismaService.commentThread.findMany(
preparedArgs, preparedArgs,
); );

View File

@ -1,4 +1,5 @@
import * as TypeGraphQL from '@nestjs/graphql'; 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 { CommentThread } from 'src/api/@generated/comment-thread/comment-thread.model';
import { Comment } from 'src/api/@generated/comment/comment.model'; import { Comment } from 'src/api/@generated/comment/comment.model';
import { PrismaService } from 'src/database/prisma.service'; 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',
},
});
}
} }