From 66585fce9ad776979bb14f7c7bf1a3e002e73a96 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Tue, 25 Jul 2023 21:58:57 -0700 Subject: [PATCH] Refactor Checkbox (#932) * Refactor Checkbox * Complete note completion * Fixes * Fix login --- front/.eslintrc.js | 14 +- front/src/generated/graphql.tsx | 15 +- front/src/index.tsx | 24 +-- .../components/CommentThreadEditor.tsx | 66 +++--- .../components/CommentThreadTitle.tsx | 82 ++++++++ .../src/modules/activities/queries/select.ts | 2 + .../src/modules/activities/queries/update.ts | 9 +- .../timeline/components/Timeline.tsx | 195 +----------------- .../timeline/components/TimelineActivity.tsx | 191 +++++++++++++++++ .../components/TimelineActivityTitle.tsx | 64 ++++++ .../modules/apollo/hooks/useApolloFactory.ts | 4 + .../components/EditableCompanyAddressCell.tsx | 12 +- .../modules/ui/input/components/Checkbox.tsx | 53 +++-- .../__stories__/AutosizeTextInput.stories.tsx | 2 +- .../__stories__/Checkbox.stories.tsx | 74 +++++++ .../__stories__/TextInput.stories.tsx | 2 +- .../ui/tooltip/OverflowingTextWithTooltip.tsx | 1 + 17 files changed, 552 insertions(+), 258 deletions(-) create mode 100644 front/src/modules/activities/components/CommentThreadTitle.tsx create mode 100644 front/src/modules/activities/timeline/components/TimelineActivity.tsx create mode 100644 front/src/modules/activities/timeline/components/TimelineActivityTitle.tsx create mode 100644 front/src/modules/ui/input/components/__stories__/Checkbox.stories.tsx diff --git a/front/.eslintrc.js b/front/.eslintrc.js index d47a49995..51c37fcc1 100644 --- a/front/.eslintrc.js +++ b/front/.eslintrc.js @@ -5,7 +5,7 @@ module.exports = { tsconfigRootDir: __dirname, sourceType: 'module', }, - plugins: ['@typescript-eslint/eslint-plugin', 'simple-import-sort', 'twenty', 'unused-imports'], + plugins: ['@typescript-eslint/eslint-plugin', 'unused-imports', 'simple-import-sort', 'twenty'], extends: [ 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', @@ -48,12 +48,12 @@ module.exports = { 'twenty/sort-css-properties-alphabetically': 'error', 'twenty/no-hardcoded-colors': 'error', 'func-style':['error', 'declaration', { 'allowArrowFunctions': true }], - "no-unused-vars": "off", "@typescript-eslint/no-unused-vars": "off", - "unused-imports/no-unused-imports": "warn", - "unused-imports/no-unused-vars": [ - "warn", - { "vars": "all", "varsIgnorePattern": "^_", "args": "after-used", "argsIgnorePattern": "^_" } - ] + "no-unused-vars": "off", + "unused-imports/no-unused-imports": "warn", + "unused-imports/no-unused-vars": [ + "warn", + { "vars": "all", "varsIgnorePattern": "^_", "args": "after-used", "argsIgnorePattern": "^_" } + ] } }; diff --git a/front/src/generated/graphql.tsx b/front/src/generated/graphql.tsx index 85062f255..30bfc9c05 100644 --- a/front/src/generated/graphql.tsx +++ b/front/src/generated/graphql.tsx @@ -1992,14 +1992,14 @@ export type GetCommentThreadsByTargetsQueryVariables = Exact<{ }>; -export type GetCommentThreadsByTargetsQuery = { __typename?: 'Query', findManyCommentThreads: Array<{ __typename?: 'CommentThread', id: string, createdAt: string, title?: string | null, body?: string | null, type: ActivityType, author: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string }, comments?: Array<{ __typename?: 'Comment', id: string, body: string, createdAt: string, updatedAt: string, author: { __typename?: 'User', id: string, displayName: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null } }> | null, commentThreadTargets?: Array<{ __typename?: 'CommentThreadTarget', id: string, commentableId: string, commentableType: CommentableType }> | null }> }; +export type GetCommentThreadsByTargetsQuery = { __typename?: 'Query', findManyCommentThreads: Array<{ __typename?: 'CommentThread', id: string, createdAt: string, title?: string | null, body?: string | null, type: ActivityType, completedAt?: string | null, author: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string }, comments?: Array<{ __typename?: 'Comment', id: string, body: string, createdAt: string, updatedAt: string, author: { __typename?: 'User', id: string, displayName: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null } }> | null, commentThreadTargets?: Array<{ __typename?: 'CommentThreadTarget', id: string, commentableId: string, commentableType: CommentableType }> | null }> }; export type GetCommentThreadQueryVariables = Exact<{ commentThreadId: Scalars['String']; }>; -export type GetCommentThreadQuery = { __typename?: 'Query', findManyCommentThreads: Array<{ __typename?: 'CommentThread', id: string, createdAt: string, body?: string | null, title?: string | null, type: ActivityType, author: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string }, comments?: Array<{ __typename?: 'Comment', id: string, body: string, createdAt: string, updatedAt: string, author: { __typename?: 'User', id: string, displayName: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null } }> | null, commentThreadTargets?: Array<{ __typename?: 'CommentThreadTarget', id: string, commentableId: string, commentableType: CommentableType }> | null }> }; +export type GetCommentThreadQuery = { __typename?: 'Query', findManyCommentThreads: Array<{ __typename?: 'CommentThread', id: string, createdAt: string, body?: string | null, title?: string | null, type: ActivityType, completedAt?: string | null, author: { __typename?: 'User', id: string, firstName?: string | null, lastName?: string | null, displayName: string }, comments?: Array<{ __typename?: 'Comment', id: string, body: string, createdAt: string, updatedAt: string, author: { __typename?: 'User', id: string, displayName: string, firstName?: string | null, lastName?: string | null, avatarUrl?: string | null } }> | null, commentThreadTargets?: Array<{ __typename?: 'CommentThreadTarget', id: string, commentableId: string, commentableType: CommentableType }> | null }> }; export type AddCommentThreadTargetsOnCommentThreadMutationVariables = Exact<{ commentThreadId: Scalars['String']; @@ -2029,10 +2029,11 @@ export type UpdateCommentThreadMutationVariables = Exact<{ body?: InputMaybe; title?: InputMaybe; type?: InputMaybe; + completedAt?: InputMaybe; }>; -export type UpdateCommentThreadMutation = { __typename?: 'Mutation', updateOneCommentThread: { __typename?: 'CommentThread', id: string, body?: string | null, title?: string | null, type: ActivityType } }; +export type UpdateCommentThreadMutation = { __typename?: 'Mutation', updateOneCommentThread: { __typename?: 'CommentThread', id: string, body?: string | null, title?: string | null, type: ActivityType, completedAt?: string | null } }; export type UploadAttachmentMutationVariables = Exact<{ file: Scalars['Upload']; @@ -2495,6 +2496,7 @@ export const GetCommentThreadsByTargetsDocument = gql` title body type + completedAt author { id firstName @@ -2559,6 +2561,7 @@ export const GetCommentThreadDocument = gql` body title type + completedAt author { id firstName @@ -2740,15 +2743,16 @@ export type DeleteCommentThreadMutationHookResult = ReturnType; export type DeleteCommentThreadMutationOptions = Apollo.BaseMutationOptions; export const UpdateCommentThreadDocument = gql` - mutation UpdateCommentThread($id: String!, $body: String, $title: String, $type: ActivityType) { + mutation UpdateCommentThread($id: String!, $body: String, $title: String, $type: ActivityType, $completedAt: DateTime) { updateOneCommentThread( where: {id: $id} - data: {body: $body, title: $title, type: $type} + data: {body: $body, title: $title, type: $type, completedAt: $completedAt} ) { id body title type + completedAt } } `; @@ -2771,6 +2775,7 @@ export type UpdateCommentThreadMutationFn = Apollo.MutationFunction - - - - - - + + + + + + - - - - - - + + + + + + , ); diff --git a/front/src/modules/activities/components/CommentThreadEditor.tsx b/front/src/modules/activities/components/CommentThreadEditor.tsx index e0dfae7aa..92e6c7099 100644 --- a/front/src/modules/activities/components/CommentThreadEditor.tsx +++ b/front/src/modules/activities/components/CommentThreadEditor.tsx @@ -21,6 +21,8 @@ import { debounce } from '~/utils/debounce'; import { CommentThreadActionBar } from '../right-drawer/components/CommentThreadActionBar'; import { CommentForDrawer } from '../types/CommentForDrawer'; +import { CommentThreadTitle } from './CommentThreadTitle'; + import '@blocknote/core/style.css'; const StyledContainer = styled.div` @@ -54,29 +56,6 @@ const StyledTopContainer = styled.div` padding: 24px 24px 24px 48px; `; -const StyledEditableTitleInput = styled.input` - background: transparent; - - border: none; - color: ${({ theme }) => theme.font.color.primary}; - display: flex; - flex: 1 0 0; - - flex-direction: column; - font-family: Inter; - font-size: ${({ theme }) => theme.font.size.xl}; - font-style: normal; - font-weight: ${({ theme }) => theme.font.weight.semiBold}; - justify-content: center; - - line-height: ${({ theme }) => theme.text.lineHeight.md}; - outline: none; - width: calc(100% - ${({ theme }) => theme.spacing(2)}); - &::placeholder { - color: ${({ theme }) => theme.font.color.light}; - } -`; - const StyledTopActionsContainer = styled.div` align-items: center; display: flex; @@ -86,7 +65,10 @@ const StyledTopActionsContainer = styled.div` `; type OwnProps = { - commentThread: Pick & { + commentThread: Pick< + CommentThread, + 'id' | 'title' | 'body' | 'type' | 'completedAt' + > & { comments?: Array | null; } & { commentThreadTargets?: Array< @@ -106,6 +88,9 @@ export function CommentThreadEditor({ useState(false); const [title, setTitle] = useState(commentThread.title ?? ''); + const [completedAt, setCompletedAt] = useState( + commentThread.completedAt ?? '', + ); const [updateCommentThreadMutation] = useUpdateCommentThreadMutation(); @@ -123,6 +108,23 @@ export function CommentThreadEditor({ }, [commentThread, updateCommentThreadMutation], ); + + const handleActivityCompletionChange = useCallback( + (value: boolean) => { + updateCommentThreadMutation({ + variables: { + id: commentThread.id, + completedAt: value ? new Date().toISOString() : null, + }, + refetchQueries: [ + getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '', + ], + }); + setCompletedAt(value ? new Date().toISOString() : null); + }, + [commentThread, updateCommentThreadMutation], + ); + const debouncedUpdateTitle = debounce(updateTitle, 200); function updateTitleFromBody(body: string) { @@ -145,16 +147,16 @@ export function CommentThreadEditor({ - { + { + setTitle(newTitle); setHasUserManuallySetTitle(true); - setTitle(event.target.value); - debouncedUpdateTitle(event.target.value); + debouncedUpdateTitle(newTitle); }} - value={title ?? ''} + onCompletionChange={handleActivityCompletionChange} /> ` + background: transparent; + + border: none; + color: ${({ theme, value }) => + value ? theme.font.color.primary : theme.font.color.light}; + display: flex; + + flex-direction: column; + font-size: ${({ theme }) => theme.font.size.xl}; + font-weight: ${({ theme }) => theme.font.weight.semiBold}; + + line-height: ${({ theme }) => theme.text.lineHeight.md}; + outline: none; + text-decoration: ${({ completed }) => (completed ? 'line-through' : 'none')}; + &::placeholder { + color: ${({ theme }) => theme.font.color.light}; + } + width: calc(100% - ${({ theme }) => theme.spacing(2)}); +`; + +const StyledContainer = styled.div` + display: flex; + flex-direction: row; + gap: ${({ theme }) => theme.spacing(2)}; +`; + +const StyledCheckboxContainer = styled.div` + align-items: center; + display: flex; + justify-content: center; +`; + +type OwnProps = { + title: string; + type: ActivityType; + completed: boolean; + onTitleChange: (title: string) => void; + onCompletionChange: (value: boolean) => void; +}; + +export function CommentThreadTitle({ + title, + completed, + type, + onTitleChange, + onCompletionChange, +}: OwnProps) { + return ( + + {type === ActivityType.Task && ( + onCompletionChange(!completed)}> + + + )} + onTitleChange(event.target.value)} + value={title} + completed={completed} + /> + + ); +} diff --git a/front/src/modules/activities/queries/select.ts b/front/src/modules/activities/queries/select.ts index 8a3db3014..914b81623 100644 --- a/front/src/modules/activities/queries/select.ts +++ b/front/src/modules/activities/queries/select.ts @@ -18,6 +18,7 @@ export const GET_COMMENT_THREADS_BY_TARGETS = gql` title body type + completedAt author { id firstName @@ -54,6 +55,7 @@ export const GET_COMMENT_THREAD = gql` body title type + completedAt author { id firstName diff --git a/front/src/modules/activities/queries/update.ts b/front/src/modules/activities/queries/update.ts index d3ad7d740..e28a66f6d 100644 --- a/front/src/modules/activities/queries/update.ts +++ b/front/src/modules/activities/queries/update.ts @@ -68,15 +68,22 @@ export const UPDATE_COMMENT_THREAD = gql` $body: String $title: String $type: ActivityType + $completedAt: DateTime ) { updateOneCommentThread( where: { id: $id } - data: { body: $body, title: $title, type: $type } + data: { + body: $body + title: $title + type: $type + completedAt: $completedAt + } ) { id body title type + completedAt } } `; diff --git a/front/src/modules/activities/timeline/components/Timeline.tsx b/front/src/modules/activities/timeline/components/Timeline.tsx index c4725f4ec..16b230d69 100644 --- a/front/src/modules/activities/timeline/components/Timeline.tsx +++ b/front/src/modules/activities/timeline/components/Timeline.tsx @@ -1,25 +1,18 @@ import React from 'react'; -import { Tooltip } from 'react-tooltip'; import styled from '@emotion/styled'; import { CommentThreadCreateButton } from '@/activities/components/CommentThreadCreateButton'; -import { useOpenCommentThreadRightDrawer } from '@/activities/hooks/useOpenCommentThreadRightDrawer'; import { useOpenCreateCommentThreadDrawer } from '@/activities/hooks/useOpenCreateCommentThreadDrawer'; import { CommentableEntity } from '@/activities/types/CommentableEntity'; import { CommentThreadForDrawer } from '@/activities/types/CommentThreadForDrawer'; import { useIsMobile } from '@/ui/hooks/useIsMobile'; -import { IconNotes } from '@/ui/icon/index'; import { ActivityType, SortOrder, useGetCommentThreadsByTargetsQuery, } from '~/generated/graphql'; -import { - beautifyExactDate, - beautifyPastDateRelativeToNow, -} from '~/utils/date-utils'; -import { OverflowingTextWithTooltip } from '../../../ui/tooltip/OverflowingTextWithTooltip'; +import { TimelineActivity } from './TimelineActivity'; const StyledMainContainer = styled.div` align-items: flex-start; @@ -72,112 +65,6 @@ const StyledEmptyTimelineSubTitle = styled.div` margin-bottom: ${({ theme }) => theme.spacing(2)}; `; -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: center; - align-items: center; - color: ${({ theme }) => theme.font.color.tertiary}; - display: flex; - flex: 1 0 0; - flex-wrap: wrap; - gap: 4px; - 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: ${({ theme }) => theme.border.radius.sm}; - 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}; - - width: 100%; -`; - -const StyledCardContent = styled.div` - align-self: stretch; - color: ${({ theme }) => theme.font.color.secondary}; - - width: 100%; -`; - -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; @@ -208,8 +95,6 @@ export function Timeline({ entity }: { entity: CommentableEntity }) { }, }); - const openCommentThreadRightDrawer = useOpenCommentThreadRightDrawer(); - const openCreateCommandThread = useOpenCreateCommentThreadDrawer(); const commentThreads: CommentThreadForDrawer[] = @@ -235,76 +120,18 @@ export function Timeline({ entity }: { entity: CommentableEntity }) { return ( - - - openCreateCommandThread(entity, ActivityType.Note) - } - onTaskClick={() => - openCreateCommandThread(entity, ActivityType.Task) - } - /> - + openCreateCommandThread(entity, ActivityType.Note)} + onTaskClick={() => openCreateCommandThread(entity, ActivityType.Task)} + /> - {commentThreads.map((commentThread) => { - const beautifiedCreatedAt = beautifyPastDateRelativeToNow( - commentThread.createdAt, - ); - const exactCreatedAt = beautifyExactDate(commentThread.createdAt); - const body = JSON.parse(commentThread.body ?? '{}')[0]?.content[0] - ?.text; - - return ( - - - - - - - {commentThread.author.displayName} - created a note - - - {beautifiedCreatedAt} ago - - - - - - - - - - openCommentThreadRightDrawer(commentThread.id) - } - > - - - - - - - - - - - ); - })} + {commentThreads.map((commentThread) => ( + + ))} ); diff --git a/front/src/modules/activities/timeline/components/TimelineActivity.tsx b/front/src/modules/activities/timeline/components/TimelineActivity.tsx new file mode 100644 index 000000000..201ada359 --- /dev/null +++ b/front/src/modules/activities/timeline/components/TimelineActivity.tsx @@ -0,0 +1,191 @@ +import { useCallback } from 'react'; +import { Tooltip } from 'react-tooltip'; +import { getOperationName } from '@apollo/client/utilities'; +import styled from '@emotion/styled'; + +import { useOpenCommentThreadRightDrawer } from '@/activities/hooks/useOpenCommentThreadRightDrawer'; +import { GET_COMMENT_THREADS_BY_TARGETS } from '@/activities/queries'; +import { IconNotes } from '@/ui/icon'; +import { OverflowingTextWithTooltip } from '@/ui/tooltip/OverflowingTextWithTooltip'; +import { + CommentThread, + useUpdateCommentThreadMutation, +} from '~/generated/graphql'; +import { + beautifyExactDate, + beautifyPastDateRelativeToNow, +} from '~/utils/date-utils'; + +import { TimelineActivityTitle } from './TimelineActivityTitle'; + +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: center; + align-items: center; + color: ${({ theme }) => theme.font.color.tertiary}; + display: flex; + gap: ${({ theme }) => theme.spacing(1)}; + 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: ${({ theme }) => theme.spacing(2)}; + justify-content: flex-end; +`; + +const StyledVerticalLineContainer = styled.div` + align-items: center; + align-self: stretch; + display: flex; + gap: ${({ theme }) => theme.spacing(2)}; + 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: ${({ theme }) => theme.spacing(2)}; + padding: 4px 0px 20px 0px; +`; + +const StyledCard = styled.div` + align-items: flex-start; + background: ${({ theme }) => theme.background.secondary}; + border: 1px solid ${({ theme }) => theme.border.color.medium}; + border-radius: ${({ theme }) => theme.border.radius.sm}; + display: flex; + flex-direction: column; + gap: ${({ theme }) => theme.spacing(3)}; + max-width: 400px; + padding: ${({ theme }) => theme.spacing(3)}; + position: relative; +`; + +const StyledCardContent = styled.div` + align-self: stretch; + color: ${({ theme }) => theme.font.color.secondary}; + + width: 100%; +`; + +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: ${({ theme }) => theme.spacing(2)}; +`; + +const StyledTimelineItemContainer = styled.div` + align-items: center; + align-self: stretch; + display: flex; + gap: 16px; +`; + +type OwnProps = { + commentThread: Pick< + CommentThread, + 'id' | 'title' | 'body' | 'createdAt' | 'completedAt' | 'type' + > & { author: Pick }; +}; + +export function TimelineActivity({ commentThread }: OwnProps) { + const beautifiedCreatedAt = beautifyPastDateRelativeToNow( + commentThread.createdAt, + ); + const exactCreatedAt = beautifyExactDate(commentThread.createdAt); + const body = JSON.parse(commentThread.body ?? '{}')[0]?.content[0]?.text; + + const openCommentThreadRightDrawer = useOpenCommentThreadRightDrawer(); + const [updateCommentThreadMutation] = useUpdateCommentThreadMutation(); + + const handleActivityCompletionChange = useCallback( + (value: boolean) => { + updateCommentThreadMutation({ + variables: { + id: commentThread.id, + completedAt: value ? new Date().toISOString() : null, + }, + refetchQueries: [ + getOperationName(GET_COMMENT_THREADS_BY_TARGETS) ?? '', + ], + }); + }, + [commentThread, updateCommentThreadMutation], + ); + + return ( + <> + + + + + + {commentThread.author.displayName} + created a note + + + {beautifiedCreatedAt} ago + + + + + + + + + openCommentThreadRightDrawer(commentThread.id)} + > + + + + + + + + + ); +} diff --git a/front/src/modules/activities/timeline/components/TimelineActivityTitle.tsx b/front/src/modules/activities/timeline/components/TimelineActivityTitle.tsx new file mode 100644 index 000000000..20c7e6b59 --- /dev/null +++ b/front/src/modules/activities/timeline/components/TimelineActivityTitle.tsx @@ -0,0 +1,64 @@ +import styled from '@emotion/styled'; + +import { Checkbox, CheckboxShape } from '@/ui/input/components/Checkbox'; +import { OverflowingTextWithTooltip } from '@/ui/tooltip/OverflowingTextWithTooltip'; +import { ActivityType } from '~/generated/graphql'; + +const StyledTitleContainer = styled.div` + color: ${({ theme }) => theme.font.color.primary}; + display: flex; + flex-direction: row; + font-weight: ${({ theme }) => theme.font.weight.medium}; + gap: ${({ theme }) => theme.spacing(2)}; + line-height: ${({ theme }) => theme.text.lineHeight.lg}; + + width: 100%; +`; + +const StyledTitleText = styled.div<{ completed?: boolean }>` + text-decoration: ${({ completed }) => (completed ? 'line-through' : 'none')}; + width: 100%; +`; + +const StyledCheckboxContainer = styled.div` + align-items: center; + display: flex; + justify-content: center; + width: 100%; +`; + +type OwnProps = { + title: string; + completed?: boolean; + type: ActivityType; + onCompletionChange?: (value: boolean) => void; +}; + +export function TimelineActivityTitle({ + title, + completed, + type, + onCompletionChange, +}: OwnProps) { + return ( + + {type === ActivityType.Task && ( + { + event.preventDefault(); + event.stopPropagation(); + onCompletionChange?.(!completed); + }} + > + + + )} + + + + + ); +} diff --git a/front/src/modules/apollo/hooks/useApolloFactory.ts b/front/src/modules/apollo/hooks/useApolloFactory.ts index 54367b298..c7a6562d2 100644 --- a/front/src/modules/apollo/hooks/useApolloFactory.ts +++ b/front/src/modules/apollo/hooks/useApolloFactory.ts @@ -1,9 +1,11 @@ import { useMemo, useRef } from 'react'; +import { useNavigate } from 'react-router-dom'; import { InMemoryCache, NormalizedCacheObject } from '@apollo/client'; import { useRecoilState } from 'recoil'; import { tokenPairState } from '@/auth/states/tokenPairState'; import { isDebugModeState } from '@/client-config/states/isDebugModeState'; +import { AppPath } from '@/types/AppPath'; import { CommentThreadTarget } from '~/generated/graphql'; import { useUpdateEffect } from '~/hooks/useUpdateEffect'; @@ -13,6 +15,7 @@ export function useApolloFactory() { const apolloRef = useRef | null>(null); const [isDebugMode] = useRecoilState(isDebugModeState); + const navigate = useNavigate(); const [tokenPair, setTokenPair] = useRecoilState(tokenPairState); const apolloClient = useMemo(() => { @@ -46,6 +49,7 @@ export function useApolloFactory() { }, onUnauthenticatedError() { setTokenPair(null); + navigate(AppPath.SignIn); }, extraLinks: [], isDebugMode, diff --git a/front/src/modules/companies/table/components/EditableCompanyAddressCell.tsx b/front/src/modules/companies/table/components/EditableCompanyAddressCell.tsx index d22636f21..b56fea888 100644 --- a/front/src/modules/companies/table/components/EditableCompanyAddressCell.tsx +++ b/front/src/modules/companies/table/components/EditableCompanyAddressCell.tsx @@ -1,3 +1,4 @@ +import { useEffect, useState } from 'react'; import { useRecoilValue } from 'recoil'; import { companyAddressFamilyState } from '@/companies/states/companyAddressFamilyState'; @@ -14,17 +15,22 @@ export function EditableCompanyAddressCell() { companyAddressFamilyState(currentRowEntityId ?? ''), ); + const [internalValue, setInternalValue] = useState(address ?? ''); + useEffect(() => { + setInternalValue(address ?? ''); + }, [address]); + return ( + value={internalValue} + onSubmit={() => updateCompany({ variables: { where: { id: currentRowEntityId, }, data: { - address: newAddress, + address: internalValue, }, }, }) diff --git a/front/src/modules/ui/input/components/Checkbox.tsx b/front/src/modules/ui/input/components/Checkbox.tsx index b07bfc444..4caec5717 100644 --- a/front/src/modules/ui/input/components/Checkbox.tsx +++ b/front/src/modules/ui/input/components/Checkbox.tsx @@ -8,11 +8,23 @@ export enum CheckboxVariant { Secondary = 'secondary', } +export enum CheckboxShape { + Squared = 'squared', + Rounded = 'rounded', +} + +export enum CheckboxSize { + Large = 'large', + Small = 'small', +} + type OwnProps = { checked: boolean; indeterminate?: boolean; onChange?: (value: boolean) => void; variant?: CheckboxVariant; + size?: CheckboxSize; + shape?: CheckboxShape; }; const StyledInputContainer = styled.div` @@ -22,8 +34,10 @@ const StyledInputContainer = styled.div` `; const StyledInput = styled.input<{ + checkboxSize: CheckboxSize; + variant: CheckboxVariant; indeterminate?: boolean; - variant?: CheckboxVariant; + shape?: CheckboxShape; }>` cursor: pointer; margin: 0; @@ -32,30 +46,37 @@ const StyledInput = styled.input<{ z-index: 10; & + label { + --size: ${({ checkboxSize }) => + checkboxSize === CheckboxSize.Large ? '18px' : '12px'}; cursor: pointer; - height: 14px; + height: calc(var(--size) + 2px); padding: 0; position: relative; - width: 14px; + width: calc(var(--size) + 2px); } & + label:before { - border-radius: ${({ theme }) => theme.border.radius.sm}; + --size: ${({ checkboxSize }) => + checkboxSize === CheckboxSize.Large ? '18px' : '12px'}; background: ${({ theme, indeterminate }) => indeterminate ? theme.color.blue : 'transparent'}; - border-style: solid; - border-width: 1px; border-color: ${({ theme, indeterminate, variant }) => indeterminate ? theme.color.blue : variant === CheckboxVariant.Primary ? theme.border.color.inverted : theme.border.color.secondaryInverted}; + border-radius: ${({ theme, shape }) => + shape === CheckboxShape.Rounded + ? theme.border.radius.rounded + : theme.border.radius.sm}; + border-style: solid; + border-width: 1px; content: ''; cursor: pointer; display: inline-block; - height: 12px; - width: 12px; + height: var(--size); + width: var(--size); } &:checked + label:before { @@ -64,12 +85,16 @@ const StyledInput = styled.input<{ } & + label > svg { - height: 12px; - left: 1px; + --padding: ${({ checkboxSize }) => + checkboxSize === CheckboxSize.Large ? '2px' : '1px'}; + --size: ${({ checkboxSize }) => + checkboxSize === CheckboxSize.Large ? '16px' : '12px'}; + height: var(--size); + left: var(--padding); position: absolute; stroke: ${({ theme }) => theme.grayScale.gray0}; - top: 1px; - width: 12px; + top: var(--padding); + width: var(--size); } `; @@ -78,6 +103,8 @@ export function Checkbox({ onChange, indeterminate, variant = CheckboxVariant.Primary, + size = CheckboxSize.Small, + shape = CheckboxShape.Squared, }: OwnProps) { const [isInternalChecked, setIsInternalChecked] = React.useState(false); @@ -101,6 +128,8 @@ export function Checkbox({ checked={isInternalChecked} indeterminate={indeterminate} variant={variant} + checkboxSize={size} + shape={shape} onChange={(event) => handleChange(event.target.checked)} />