* Begin adding show view and refactoring threads to become notes * Progress on design * Progress redesign timeline * Dropdown button, design improvement * Open comment thread edit mode in drawer * Autosave local storage and commentThreadcount * Improve display and fix missing key issue * Remove some hardcoded CSS properties * Create button * Split company show into ui/business + fix eslint * Fix font weight * Begin auto-save on edit mode * Save server-side query result to Apollo cache * Fix save behavior * Refetch timeline after creating note * Rename createCommentThreadWithComment * Improve styling * Revert "Improve styling" This reverts commit 9fbbf2db006e529330edc64f3eb8ff9ecdde6bb0. * Improve CSS styling * Bring back border radius inadvertently removed * padding adjustment * Improve blocknote design * Improve edit mode display * Remove Comments.tsx * Remove irrelevant comment stories * Removed un-necessary panel component * stop using fragment, move trash icon * Add a basic story for CompanyShow * Add a basic People show view * Fix storybook tests * Add very basic Person story * Refactor PR1 * Refactor part 2 * Refactor part 3 * Refactor part 4 * Fix tests --------- Co-authored-by: Charles Bochet <charles@twenty.com>
289 lines
8.2 KiB
TypeScript
289 lines
8.2 KiB
TypeScript
import React from 'react';
|
|
import { Tooltip } from 'react-tooltip';
|
|
import styled from '@emotion/styled';
|
|
|
|
import { useOpenCommentThreadRightDrawer } from '@/comments/hooks/useOpenCommentThreadRightDrawer';
|
|
import { useOpenCreateCommentThreadDrawer } from '@/comments/hooks/useOpenCreateCommentThreadDrawer';
|
|
import { CommentableEntity } from '@/comments/types/CommentableEntity';
|
|
import { CommentThreadForDrawer } from '@/comments/types/CommentThreadForDrawer';
|
|
import { TableActionBarButtonToggleComments } from '@/ui/components/table/action-bar/TableActionBarButtonOpenComments';
|
|
import { IconCirclePlus, IconNotes } from '@/ui/icons/index';
|
|
import {
|
|
beautifyExactDate,
|
|
beautifyPastDateRelativeToNow,
|
|
} from '@/utils/datetime/date-utils';
|
|
import {
|
|
SortOrder,
|
|
useGetCommentThreadsByTargetsQuery,
|
|
} from '~/generated/graphql';
|
|
|
|
const StyledMainContainer = styled.div`
|
|
align-items: flex-start;
|
|
align-self: stretch;
|
|
display: flex;
|
|
flex: 1 0 0;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
`;
|
|
|
|
const StyledTimelineContainer = styled.div`
|
|
align-items: center;
|
|
align-self: stretch;
|
|
display: flex;
|
|
flex: 1 0 0;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
justify-content: flex-start;
|
|
overflow-y: auto;
|
|
padding: 12px 16px 12px 16px;
|
|
`;
|
|
|
|
const StyledTimelineEmptyContainer = styled.div`
|
|
align-items: center;
|
|
align-self: stretch;
|
|
display: flex;
|
|
flex: 1 0 0;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
justify-content: center;
|
|
`;
|
|
|
|
const StyledEmptyTimelineTitle = styled.div`
|
|
color: ${({ theme }) => theme.font.color.secondary};
|
|
font-size: ${({ theme }) => theme.font.size.xxl};
|
|
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
|
line-height: ${({ theme }) => theme.text.lineHeight.md};
|
|
`;
|
|
|
|
const StyledEmptyTimelineSubTitle = styled.div`
|
|
color: ${({ theme }) => theme.font.color.extraLight};
|
|
font-size: ${({ theme }) => theme.font.size.xxl};
|
|
font-weight: ${({ theme }) => theme.font.weight.semiBold};
|
|
line-height: ${({ theme }) => theme.text.lineHeight.md};
|
|
`;
|
|
|
|
const StyledTimelineItemContainer = styled.div`
|
|
align-items: center;
|
|
align-self: stretch;
|
|
display: flex;
|
|
gap: 16px;
|
|
`;
|
|
|
|
const StyledIconContainer = styled.div`
|
|
align-items: center;
|
|
color: ${({ theme }) => theme.font.color.tertiary};
|
|
display: flex;
|
|
height: 20px;
|
|
justify-content: center;
|
|
width: 20px;
|
|
`;
|
|
|
|
const StyledItemTitleContainer = styled.div`
|
|
align-content: flex-start;
|
|
align-items: center;
|
|
color: ${({ theme }) => theme.font.color.tertiary};
|
|
display: flex;
|
|
flex: 1 0 0;
|
|
flex-wrap: wrap;
|
|
gap: 4px 8px;
|
|
height: 20px;
|
|
span {
|
|
color: ${({ theme }) => theme.font.color.secondary};
|
|
}
|
|
`;
|
|
|
|
const StyledItemTitleDate = styled.div`
|
|
align-items: center;
|
|
color: ${({ theme }) => theme.font.color.tertiary};
|
|
display: flex;
|
|
gap: 8px;
|
|
justify-content: flex-end;
|
|
`;
|
|
|
|
const StyledVerticalLineContainer = styled.div`
|
|
align-items: center;
|
|
align-self: stretch;
|
|
display: flex;
|
|
gap: 8px;
|
|
justify-content: center;
|
|
width: 20px;
|
|
`;
|
|
|
|
const StyledVerticalLine = styled.div`
|
|
align-self: stretch;
|
|
background: ${({ theme }) => theme.border.color.light};
|
|
flex-shrink: 0;
|
|
width: 2px;
|
|
`;
|
|
|
|
const StyledCardContainer = styled.div`
|
|
align-items: center;
|
|
cursor: pointer;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
padding: 4px 0px 20px 0px;
|
|
`;
|
|
|
|
const StyledCard = styled.div`
|
|
align-items: flex-start;
|
|
align-self: stretch;
|
|
background: ${({ theme }) => theme.background.secondary};
|
|
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
|
border-radius: 4px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
max-width: 400px;
|
|
padding: 12px;
|
|
`;
|
|
|
|
const StyledCardTitle = styled.div`
|
|
color: ${({ theme }) => theme.font.color.primary};
|
|
font-weight: ${({ theme }) => theme.font.weight.medium};
|
|
line-height: ${({ theme }) => theme.text.lineHeight.lg};
|
|
`;
|
|
|
|
const StyledCardContent = styled.div`
|
|
-webkit-box-orient: vertical;
|
|
|
|
-webkit-line-clamp: 3;
|
|
align-self: stretch;
|
|
color: ${({ theme }) => theme.font.color.secondary};
|
|
display: -webkit-box;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
`;
|
|
|
|
const StyledTooltip = styled(Tooltip)`
|
|
background-color: ${({ theme }) => theme.background.primary};
|
|
|
|
box-shadow: 0px 2px 4px 3px
|
|
${({ theme }) => theme.background.transparent.light};
|
|
|
|
box-shadow: 2px 4px 16px 6px
|
|
${({ theme }) => theme.background.transparent.light};
|
|
|
|
color: ${({ theme }) => theme.font.color.primary};
|
|
|
|
opacity: 1;
|
|
padding: 8px;
|
|
`;
|
|
|
|
const StyledTopActionBar = styled.div`
|
|
align-items: flex-start;
|
|
align-self: stretch;
|
|
backdrop-filter: blur(5px);
|
|
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
|
border-top-right-radius: 8px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
left: 0px;
|
|
padding: 12px 16px 12px 16px;
|
|
position: sticky;
|
|
top: 0px;
|
|
`;
|
|
|
|
export function Timeline({ entity }: { entity: CommentableEntity }) {
|
|
const { data: queryResult } = useGetCommentThreadsByTargetsQuery({
|
|
variables: {
|
|
commentThreadTargetIds: [entity.id],
|
|
orderBy: [
|
|
{
|
|
createdAt: SortOrder.Desc,
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
const openCommentThreadRightDrawer = useOpenCommentThreadRightDrawer();
|
|
|
|
const openCreateCommandThread = useOpenCreateCommentThreadDrawer();
|
|
|
|
const commentThreads: CommentThreadForDrawer[] =
|
|
queryResult?.findManyCommentThreads ?? [];
|
|
|
|
if (!commentThreads.length) {
|
|
return (
|
|
<StyledTimelineEmptyContainer>
|
|
<StyledEmptyTimelineTitle>No activity yet</StyledEmptyTimelineTitle>
|
|
<StyledEmptyTimelineSubTitle>Create one:</StyledEmptyTimelineSubTitle>
|
|
<TableActionBarButtonToggleComments
|
|
onClick={() => openCreateCommandThread(entity)}
|
|
/>
|
|
</StyledTimelineEmptyContainer>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<StyledMainContainer>
|
|
<StyledTopActionBar>
|
|
<StyledTimelineItemContainer>
|
|
<StyledIconContainer>
|
|
<IconCirclePlus />
|
|
</StyledIconContainer>
|
|
|
|
<TableActionBarButtonToggleComments
|
|
onClick={() => openCreateCommandThread(entity)}
|
|
/>
|
|
</StyledTimelineItemContainer>
|
|
</StyledTopActionBar>
|
|
<StyledTimelineContainer>
|
|
{commentThreads.map((commentThread) => {
|
|
const beautifiedCreatedAt = beautifyPastDateRelativeToNow(
|
|
commentThread.createdAt,
|
|
);
|
|
const exactCreatedAt = beautifyExactDate(commentThread.createdAt);
|
|
const body = JSON.parse(commentThread.body ?? '{}')[0]?.content[0]
|
|
?.text;
|
|
|
|
return (
|
|
<React.Fragment key={commentThread.id}>
|
|
<StyledTimelineItemContainer>
|
|
<StyledIconContainer>
|
|
<IconNotes />
|
|
</StyledIconContainer>
|
|
<StyledItemTitleContainer>
|
|
<span>
|
|
{commentThread.author.firstName}{' '}
|
|
{commentThread.author.lastName}
|
|
</span>
|
|
created a note
|
|
</StyledItemTitleContainer>
|
|
<StyledItemTitleDate id={`id-${commentThread.id}`}>
|
|
{beautifiedCreatedAt} ago
|
|
</StyledItemTitleDate>
|
|
<StyledTooltip
|
|
anchorSelect={`#id-${commentThread.id}`}
|
|
content={exactCreatedAt}
|
|
clickable
|
|
noArrow
|
|
/>
|
|
</StyledTimelineItemContainer>
|
|
<StyledTimelineItemContainer>
|
|
<StyledVerticalLineContainer>
|
|
<StyledVerticalLine></StyledVerticalLine>
|
|
</StyledVerticalLineContainer>
|
|
<StyledCardContainer>
|
|
<StyledCard
|
|
onClick={() =>
|
|
openCommentThreadRightDrawer(commentThread.id)
|
|
}
|
|
>
|
|
<StyledCardTitle>
|
|
{commentThread.title ? commentThread.title : '(No title)'}
|
|
</StyledCardTitle>
|
|
<StyledCardContent>
|
|
{body ? body : '(No content)'}
|
|
</StyledCardContent>
|
|
</StyledCard>
|
|
</StyledCardContainer>
|
|
</StyledTimelineItemContainer>
|
|
</React.Fragment>
|
|
);
|
|
})}
|
|
</StyledTimelineContainer>
|
|
</StyledMainContainer>
|
|
);
|
|
}
|