Refactor Checkbox (#932)
* Refactor Checkbox * Complete note completion * Fixes * Fix login
This commit is contained in:
@ -5,7 +5,7 @@ module.exports = {
|
|||||||
tsconfigRootDir: __dirname,
|
tsconfigRootDir: __dirname,
|
||||||
sourceType: 'module',
|
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: [
|
extends: [
|
||||||
'plugin:@typescript-eslint/recommended',
|
'plugin:@typescript-eslint/recommended',
|
||||||
'plugin:prettier/recommended',
|
'plugin:prettier/recommended',
|
||||||
@ -48,12 +48,12 @@ module.exports = {
|
|||||||
'twenty/sort-css-properties-alphabetically': 'error',
|
'twenty/sort-css-properties-alphabetically': 'error',
|
||||||
'twenty/no-hardcoded-colors': 'error',
|
'twenty/no-hardcoded-colors': 'error',
|
||||||
'func-style':['error', 'declaration', { 'allowArrowFunctions': true }],
|
'func-style':['error', 'declaration', { 'allowArrowFunctions': true }],
|
||||||
"no-unused-vars": "off",
|
|
||||||
"@typescript-eslint/no-unused-vars": "off",
|
"@typescript-eslint/no-unused-vars": "off",
|
||||||
"unused-imports/no-unused-imports": "warn",
|
"no-unused-vars": "off",
|
||||||
"unused-imports/no-unused-vars": [
|
"unused-imports/no-unused-imports": "warn",
|
||||||
"warn",
|
"unused-imports/no-unused-vars": [
|
||||||
{ "vars": "all", "varsIgnorePattern": "^_", "args": "after-used", "argsIgnorePattern": "^_" }
|
"warn",
|
||||||
]
|
{ "vars": "all", "varsIgnorePattern": "^_", "args": "after-used", "argsIgnorePattern": "^_" }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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<{
|
export type GetCommentThreadQueryVariables = Exact<{
|
||||||
commentThreadId: Scalars['String'];
|
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<{
|
export type AddCommentThreadTargetsOnCommentThreadMutationVariables = Exact<{
|
||||||
commentThreadId: Scalars['String'];
|
commentThreadId: Scalars['String'];
|
||||||
@ -2029,10 +2029,11 @@ export type UpdateCommentThreadMutationVariables = Exact<{
|
|||||||
body?: InputMaybe<Scalars['String']>;
|
body?: InputMaybe<Scalars['String']>;
|
||||||
title?: InputMaybe<Scalars['String']>;
|
title?: InputMaybe<Scalars['String']>;
|
||||||
type?: InputMaybe<ActivityType>;
|
type?: InputMaybe<ActivityType>;
|
||||||
|
completedAt?: InputMaybe<Scalars['DateTime']>;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
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<{
|
export type UploadAttachmentMutationVariables = Exact<{
|
||||||
file: Scalars['Upload'];
|
file: Scalars['Upload'];
|
||||||
@ -2495,6 +2496,7 @@ export const GetCommentThreadsByTargetsDocument = gql`
|
|||||||
title
|
title
|
||||||
body
|
body
|
||||||
type
|
type
|
||||||
|
completedAt
|
||||||
author {
|
author {
|
||||||
id
|
id
|
||||||
firstName
|
firstName
|
||||||
@ -2559,6 +2561,7 @@ export const GetCommentThreadDocument = gql`
|
|||||||
body
|
body
|
||||||
title
|
title
|
||||||
type
|
type
|
||||||
|
completedAt
|
||||||
author {
|
author {
|
||||||
id
|
id
|
||||||
firstName
|
firstName
|
||||||
@ -2740,15 +2743,16 @@ export type DeleteCommentThreadMutationHookResult = ReturnType<typeof useDeleteC
|
|||||||
export type DeleteCommentThreadMutationResult = Apollo.MutationResult<DeleteCommentThreadMutation>;
|
export type DeleteCommentThreadMutationResult = Apollo.MutationResult<DeleteCommentThreadMutation>;
|
||||||
export type DeleteCommentThreadMutationOptions = Apollo.BaseMutationOptions<DeleteCommentThreadMutation, DeleteCommentThreadMutationVariables>;
|
export type DeleteCommentThreadMutationOptions = Apollo.BaseMutationOptions<DeleteCommentThreadMutation, DeleteCommentThreadMutationVariables>;
|
||||||
export const UpdateCommentThreadDocument = gql`
|
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(
|
updateOneCommentThread(
|
||||||
where: {id: $id}
|
where: {id: $id}
|
||||||
data: {body: $body, title: $title, type: $type}
|
data: {body: $body, title: $title, type: $type, completedAt: $completedAt}
|
||||||
) {
|
) {
|
||||||
id
|
id
|
||||||
body
|
body
|
||||||
title
|
title
|
||||||
type
|
type
|
||||||
|
completedAt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@ -2771,6 +2775,7 @@ export type UpdateCommentThreadMutationFn = Apollo.MutationFunction<UpdateCommen
|
|||||||
* body: // value for 'body'
|
* body: // value for 'body'
|
||||||
* title: // value for 'title'
|
* title: // value for 'title'
|
||||||
* type: // value for 'type'
|
* type: // value for 'type'
|
||||||
|
* completedAt: // value for 'completedAt'
|
||||||
* },
|
* },
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -24,22 +24,22 @@ const root = ReactDOM.createRoot(
|
|||||||
|
|
||||||
root.render(
|
root.render(
|
||||||
<RecoilRoot>
|
<RecoilRoot>
|
||||||
<ApolloProvider>
|
<BrowserRouter>
|
||||||
<UserProvider>
|
<ApolloProvider>
|
||||||
<ClientConfigProvider>
|
<UserProvider>
|
||||||
<AppThemeProvider>
|
<ClientConfigProvider>
|
||||||
<SnackBarProvider>
|
<AppThemeProvider>
|
||||||
<BrowserRouter>
|
<SnackBarProvider>
|
||||||
<AuthAutoRouter />
|
<AuthAutoRouter />
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<App />
|
<App />
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
</BrowserRouter>
|
</SnackBarProvider>
|
||||||
</SnackBarProvider>
|
</AppThemeProvider>
|
||||||
</AppThemeProvider>
|
</ClientConfigProvider>
|
||||||
</ClientConfigProvider>
|
</UserProvider>
|
||||||
</UserProvider>
|
</ApolloProvider>
|
||||||
</ApolloProvider>
|
</BrowserRouter>
|
||||||
</RecoilRoot>,
|
</RecoilRoot>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -21,6 +21,8 @@ import { debounce } from '~/utils/debounce';
|
|||||||
import { CommentThreadActionBar } from '../right-drawer/components/CommentThreadActionBar';
|
import { CommentThreadActionBar } from '../right-drawer/components/CommentThreadActionBar';
|
||||||
import { CommentForDrawer } from '../types/CommentForDrawer';
|
import { CommentForDrawer } from '../types/CommentForDrawer';
|
||||||
|
|
||||||
|
import { CommentThreadTitle } from './CommentThreadTitle';
|
||||||
|
|
||||||
import '@blocknote/core/style.css';
|
import '@blocknote/core/style.css';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
@ -54,29 +56,6 @@ const StyledTopContainer = styled.div`
|
|||||||
padding: 24px 24px 24px 48px;
|
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`
|
const StyledTopActionsContainer = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -86,7 +65,10 @@ const StyledTopActionsContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
commentThread: Pick<CommentThread, 'id' | 'title' | 'body' | 'type'> & {
|
commentThread: Pick<
|
||||||
|
CommentThread,
|
||||||
|
'id' | 'title' | 'body' | 'type' | 'completedAt'
|
||||||
|
> & {
|
||||||
comments?: Array<CommentForDrawer> | null;
|
comments?: Array<CommentForDrawer> | null;
|
||||||
} & {
|
} & {
|
||||||
commentThreadTargets?: Array<
|
commentThreadTargets?: Array<
|
||||||
@ -106,6 +88,9 @@ export function CommentThreadEditor({
|
|||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
|
|
||||||
const [title, setTitle] = useState<string | null>(commentThread.title ?? '');
|
const [title, setTitle] = useState<string | null>(commentThread.title ?? '');
|
||||||
|
const [completedAt, setCompletedAt] = useState<string | null>(
|
||||||
|
commentThread.completedAt ?? '',
|
||||||
|
);
|
||||||
|
|
||||||
const [updateCommentThreadMutation] = useUpdateCommentThreadMutation();
|
const [updateCommentThreadMutation] = useUpdateCommentThreadMutation();
|
||||||
|
|
||||||
@ -123,6 +108,23 @@ export function CommentThreadEditor({
|
|||||||
},
|
},
|
||||||
[commentThread, updateCommentThreadMutation],
|
[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);
|
const debouncedUpdateTitle = debounce(updateTitle, 200);
|
||||||
|
|
||||||
function updateTitleFromBody(body: string) {
|
function updateTitleFromBody(body: string) {
|
||||||
@ -145,16 +147,16 @@ export function CommentThreadEditor({
|
|||||||
<CommentThreadTypeDropdown commentThread={commentThread} />
|
<CommentThreadTypeDropdown commentThread={commentThread} />
|
||||||
<CommentThreadActionBar commentThreadId={commentThread?.id ?? ''} />
|
<CommentThreadActionBar commentThreadId={commentThread?.id ?? ''} />
|
||||||
</StyledTopActionsContainer>
|
</StyledTopActionsContainer>
|
||||||
<StyledEditableTitleInput
|
<CommentThreadTitle
|
||||||
autoComplete="off"
|
title={title ?? ''}
|
||||||
autoFocus
|
completed={!!completedAt}
|
||||||
placeholder={`${commentThread.type} title (optional)`}
|
type={commentThread.type}
|
||||||
onChange={(event) => {
|
onTitleChange={(newTitle) => {
|
||||||
|
setTitle(newTitle);
|
||||||
setHasUserManuallySetTitle(true);
|
setHasUserManuallySetTitle(true);
|
||||||
setTitle(event.target.value);
|
debouncedUpdateTitle(newTitle);
|
||||||
debouncedUpdateTitle(event.target.value);
|
|
||||||
}}
|
}}
|
||||||
value={title ?? ''}
|
onCompletionChange={handleActivityCompletionChange}
|
||||||
/>
|
/>
|
||||||
<PropertyBox>
|
<PropertyBox>
|
||||||
<PropertyBoxItem
|
<PropertyBoxItem
|
||||||
|
|||||||
@ -0,0 +1,82 @@
|
|||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Checkbox,
|
||||||
|
CheckboxShape,
|
||||||
|
CheckboxSize,
|
||||||
|
} from '@/ui/input/components/Checkbox';
|
||||||
|
import { ActivityType } from '~/generated/graphql';
|
||||||
|
|
||||||
|
const StyledEditableTitleInput = styled.input<{
|
||||||
|
completed: boolean;
|
||||||
|
value: string;
|
||||||
|
}>`
|
||||||
|
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 (
|
||||||
|
<StyledContainer>
|
||||||
|
{type === ActivityType.Task && (
|
||||||
|
<StyledCheckboxContainer onClick={() => onCompletionChange(!completed)}>
|
||||||
|
<Checkbox
|
||||||
|
size={CheckboxSize.Large}
|
||||||
|
shape={CheckboxShape.Rounded}
|
||||||
|
checked={completed}
|
||||||
|
/>
|
||||||
|
</StyledCheckboxContainer>
|
||||||
|
)}
|
||||||
|
<StyledEditableTitleInput
|
||||||
|
autoComplete="off"
|
||||||
|
autoFocus
|
||||||
|
placeholder={`${type} title (optional)`}
|
||||||
|
onChange={(event) => onTitleChange(event.target.value)}
|
||||||
|
value={title}
|
||||||
|
completed={completed}
|
||||||
|
/>
|
||||||
|
</StyledContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -18,6 +18,7 @@ export const GET_COMMENT_THREADS_BY_TARGETS = gql`
|
|||||||
title
|
title
|
||||||
body
|
body
|
||||||
type
|
type
|
||||||
|
completedAt
|
||||||
author {
|
author {
|
||||||
id
|
id
|
||||||
firstName
|
firstName
|
||||||
@ -54,6 +55,7 @@ export const GET_COMMENT_THREAD = gql`
|
|||||||
body
|
body
|
||||||
title
|
title
|
||||||
type
|
type
|
||||||
|
completedAt
|
||||||
author {
|
author {
|
||||||
id
|
id
|
||||||
firstName
|
firstName
|
||||||
|
|||||||
@ -68,15 +68,22 @@ export const UPDATE_COMMENT_THREAD = gql`
|
|||||||
$body: String
|
$body: String
|
||||||
$title: String
|
$title: String
|
||||||
$type: ActivityType
|
$type: ActivityType
|
||||||
|
$completedAt: DateTime
|
||||||
) {
|
) {
|
||||||
updateOneCommentThread(
|
updateOneCommentThread(
|
||||||
where: { id: $id }
|
where: { id: $id }
|
||||||
data: { body: $body, title: $title, type: $type }
|
data: {
|
||||||
|
body: $body
|
||||||
|
title: $title
|
||||||
|
type: $type
|
||||||
|
completedAt: $completedAt
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
id
|
id
|
||||||
body
|
body
|
||||||
title
|
title
|
||||||
type
|
type
|
||||||
|
completedAt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -1,25 +1,18 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Tooltip } from 'react-tooltip';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { CommentThreadCreateButton } from '@/activities/components/CommentThreadCreateButton';
|
import { CommentThreadCreateButton } from '@/activities/components/CommentThreadCreateButton';
|
||||||
import { useOpenCommentThreadRightDrawer } from '@/activities/hooks/useOpenCommentThreadRightDrawer';
|
|
||||||
import { useOpenCreateCommentThreadDrawer } from '@/activities/hooks/useOpenCreateCommentThreadDrawer';
|
import { useOpenCreateCommentThreadDrawer } from '@/activities/hooks/useOpenCreateCommentThreadDrawer';
|
||||||
import { CommentableEntity } from '@/activities/types/CommentableEntity';
|
import { CommentableEntity } from '@/activities/types/CommentableEntity';
|
||||||
import { CommentThreadForDrawer } from '@/activities/types/CommentThreadForDrawer';
|
import { CommentThreadForDrawer } from '@/activities/types/CommentThreadForDrawer';
|
||||||
import { useIsMobile } from '@/ui/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/hooks/useIsMobile';
|
||||||
import { IconNotes } from '@/ui/icon/index';
|
|
||||||
import {
|
import {
|
||||||
ActivityType,
|
ActivityType,
|
||||||
SortOrder,
|
SortOrder,
|
||||||
useGetCommentThreadsByTargetsQuery,
|
useGetCommentThreadsByTargetsQuery,
|
||||||
} from '~/generated/graphql';
|
} from '~/generated/graphql';
|
||||||
import {
|
|
||||||
beautifyExactDate,
|
|
||||||
beautifyPastDateRelativeToNow,
|
|
||||||
} from '~/utils/date-utils';
|
|
||||||
|
|
||||||
import { OverflowingTextWithTooltip } from '../../../ui/tooltip/OverflowingTextWithTooltip';
|
import { TimelineActivity } from './TimelineActivity';
|
||||||
|
|
||||||
const StyledMainContainer = styled.div`
|
const StyledMainContainer = styled.div`
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
@ -72,112 +65,6 @@ const StyledEmptyTimelineSubTitle = styled.div`
|
|||||||
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
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`
|
const StyledTopActionBar = styled.div`
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
@ -208,8 +95,6 @@ export function Timeline({ entity }: { entity: CommentableEntity }) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const openCommentThreadRightDrawer = useOpenCommentThreadRightDrawer();
|
|
||||||
|
|
||||||
const openCreateCommandThread = useOpenCreateCommentThreadDrawer();
|
const openCreateCommandThread = useOpenCreateCommentThreadDrawer();
|
||||||
|
|
||||||
const commentThreads: CommentThreadForDrawer[] =
|
const commentThreads: CommentThreadForDrawer[] =
|
||||||
@ -235,76 +120,18 @@ export function Timeline({ entity }: { entity: CommentableEntity }) {
|
|||||||
return (
|
return (
|
||||||
<StyledMainContainer>
|
<StyledMainContainer>
|
||||||
<StyledTopActionBar>
|
<StyledTopActionBar>
|
||||||
<StyledTimelineItemContainer>
|
<CommentThreadCreateButton
|
||||||
<CommentThreadCreateButton
|
onNoteClick={() => openCreateCommandThread(entity, ActivityType.Note)}
|
||||||
onNoteClick={() =>
|
onTaskClick={() => openCreateCommandThread(entity, ActivityType.Task)}
|
||||||
openCreateCommandThread(entity, ActivityType.Note)
|
/>
|
||||||
}
|
|
||||||
onTaskClick={() =>
|
|
||||||
openCreateCommandThread(entity, ActivityType.Task)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</StyledTimelineItemContainer>
|
|
||||||
</StyledTopActionBar>
|
</StyledTopActionBar>
|
||||||
<StyledTimelineContainer>
|
<StyledTimelineContainer>
|
||||||
{commentThreads.map((commentThread) => {
|
{commentThreads.map((commentThread) => (
|
||||||
const beautifiedCreatedAt = beautifyPastDateRelativeToNow(
|
<TimelineActivity
|
||||||
commentThread.createdAt,
|
key={commentThread.id}
|
||||||
);
|
commentThread={commentThread}
|
||||||
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.displayName}</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>
|
|
||||||
<OverflowingTextWithTooltip
|
|
||||||
text={
|
|
||||||
commentThread.title
|
|
||||||
? commentThread.title
|
|
||||||
: '(No title)'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</StyledCardTitle>
|
|
||||||
<StyledCardContent>
|
|
||||||
<OverflowingTextWithTooltip
|
|
||||||
text={body ? body : '(No content)'}
|
|
||||||
/>
|
|
||||||
</StyledCardContent>
|
|
||||||
</StyledCard>
|
|
||||||
</StyledCardContainer>
|
|
||||||
</StyledTimelineItemContainer>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</StyledTimelineContainer>
|
</StyledTimelineContainer>
|
||||||
</StyledMainContainer>
|
</StyledMainContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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<CommentThread['author'], 'displayName'> };
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<StyledTimelineItemContainer>
|
||||||
|
<StyledIconContainer>
|
||||||
|
<IconNotes />
|
||||||
|
</StyledIconContainer>
|
||||||
|
<StyledItemTitleContainer>
|
||||||
|
<span>{commentThread.author.displayName}</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)}
|
||||||
|
>
|
||||||
|
<TimelineActivityTitle
|
||||||
|
title={commentThread.title ?? ''}
|
||||||
|
completed={!!commentThread.completedAt}
|
||||||
|
type={commentThread.type}
|
||||||
|
onCompletionChange={handleActivityCompletionChange}
|
||||||
|
/>
|
||||||
|
<StyledCardContent>
|
||||||
|
<OverflowingTextWithTooltip text={body ? body : '(No content)'} />
|
||||||
|
</StyledCardContent>
|
||||||
|
</StyledCard>
|
||||||
|
</StyledCardContainer>
|
||||||
|
</StyledTimelineItemContainer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -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 (
|
||||||
|
<StyledTitleContainer>
|
||||||
|
{type === ActivityType.Task && (
|
||||||
|
<StyledCheckboxContainer
|
||||||
|
onClick={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
onCompletionChange?.(!completed);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={completed ?? false}
|
||||||
|
shape={CheckboxShape.Rounded}
|
||||||
|
/>
|
||||||
|
</StyledCheckboxContainer>
|
||||||
|
)}
|
||||||
|
<StyledTitleText completed={completed}>
|
||||||
|
<OverflowingTextWithTooltip text={title ? title : '(No title)'} />
|
||||||
|
</StyledTitleText>
|
||||||
|
</StyledTitleContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,9 +1,11 @@
|
|||||||
import { useMemo, useRef } from 'react';
|
import { useMemo, useRef } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { InMemoryCache, NormalizedCacheObject } from '@apollo/client';
|
import { InMemoryCache, NormalizedCacheObject } from '@apollo/client';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { tokenPairState } from '@/auth/states/tokenPairState';
|
import { tokenPairState } from '@/auth/states/tokenPairState';
|
||||||
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
|
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
|
||||||
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { CommentThreadTarget } from '~/generated/graphql';
|
import { CommentThreadTarget } from '~/generated/graphql';
|
||||||
import { useUpdateEffect } from '~/hooks/useUpdateEffect';
|
import { useUpdateEffect } from '~/hooks/useUpdateEffect';
|
||||||
|
|
||||||
@ -13,6 +15,7 @@ export function useApolloFactory() {
|
|||||||
const apolloRef = useRef<ApolloFactory<NormalizedCacheObject> | null>(null);
|
const apolloRef = useRef<ApolloFactory<NormalizedCacheObject> | null>(null);
|
||||||
const [isDebugMode] = useRecoilState(isDebugModeState);
|
const [isDebugMode] = useRecoilState(isDebugModeState);
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
const [tokenPair, setTokenPair] = useRecoilState(tokenPairState);
|
const [tokenPair, setTokenPair] = useRecoilState(tokenPairState);
|
||||||
|
|
||||||
const apolloClient = useMemo(() => {
|
const apolloClient = useMemo(() => {
|
||||||
@ -46,6 +49,7 @@ export function useApolloFactory() {
|
|||||||
},
|
},
|
||||||
onUnauthenticatedError() {
|
onUnauthenticatedError() {
|
||||||
setTokenPair(null);
|
setTokenPair(null);
|
||||||
|
navigate(AppPath.SignIn);
|
||||||
},
|
},
|
||||||
extraLinks: [],
|
extraLinks: [],
|
||||||
isDebugMode,
|
isDebugMode,
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { companyAddressFamilyState } from '@/companies/states/companyAddressFamilyState';
|
import { companyAddressFamilyState } from '@/companies/states/companyAddressFamilyState';
|
||||||
@ -14,17 +15,22 @@ export function EditableCompanyAddressCell() {
|
|||||||
companyAddressFamilyState(currentRowEntityId ?? ''),
|
companyAddressFamilyState(currentRowEntityId ?? ''),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [internalValue, setInternalValue] = useState(address ?? '');
|
||||||
|
useEffect(() => {
|
||||||
|
setInternalValue(address ?? '');
|
||||||
|
}, [address]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EditableCellText
|
<EditableCellText
|
||||||
value={address || ''}
|
value={internalValue}
|
||||||
onSubmit={(newAddress) =>
|
onSubmit={() =>
|
||||||
updateCompany({
|
updateCompany({
|
||||||
variables: {
|
variables: {
|
||||||
where: {
|
where: {
|
||||||
id: currentRowEntityId,
|
id: currentRowEntityId,
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
address: newAddress,
|
address: internalValue,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@ -8,11 +8,23 @@ export enum CheckboxVariant {
|
|||||||
Secondary = 'secondary',
|
Secondary = 'secondary',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum CheckboxShape {
|
||||||
|
Squared = 'squared',
|
||||||
|
Rounded = 'rounded',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CheckboxSize {
|
||||||
|
Large = 'large',
|
||||||
|
Small = 'small',
|
||||||
|
}
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
indeterminate?: boolean;
|
indeterminate?: boolean;
|
||||||
onChange?: (value: boolean) => void;
|
onChange?: (value: boolean) => void;
|
||||||
variant?: CheckboxVariant;
|
variant?: CheckboxVariant;
|
||||||
|
size?: CheckboxSize;
|
||||||
|
shape?: CheckboxShape;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledInputContainer = styled.div`
|
const StyledInputContainer = styled.div`
|
||||||
@ -22,8 +34,10 @@ const StyledInputContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledInput = styled.input<{
|
const StyledInput = styled.input<{
|
||||||
|
checkboxSize: CheckboxSize;
|
||||||
|
variant: CheckboxVariant;
|
||||||
indeterminate?: boolean;
|
indeterminate?: boolean;
|
||||||
variant?: CheckboxVariant;
|
shape?: CheckboxShape;
|
||||||
}>`
|
}>`
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -32,30 +46,37 @@ const StyledInput = styled.input<{
|
|||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
|
||||||
& + label {
|
& + label {
|
||||||
|
--size: ${({ checkboxSize }) =>
|
||||||
|
checkboxSize === CheckboxSize.Large ? '18px' : '12px'};
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
height: 14px;
|
height: calc(var(--size) + 2px);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 14px;
|
width: calc(var(--size) + 2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
& + label:before {
|
& + label:before {
|
||||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
--size: ${({ checkboxSize }) =>
|
||||||
|
checkboxSize === CheckboxSize.Large ? '18px' : '12px'};
|
||||||
background: ${({ theme, indeterminate }) =>
|
background: ${({ theme, indeterminate }) =>
|
||||||
indeterminate ? theme.color.blue : 'transparent'};
|
indeterminate ? theme.color.blue : 'transparent'};
|
||||||
border-style: solid;
|
|
||||||
border-width: 1px;
|
|
||||||
border-color: ${({ theme, indeterminate, variant }) =>
|
border-color: ${({ theme, indeterminate, variant }) =>
|
||||||
indeterminate
|
indeterminate
|
||||||
? theme.color.blue
|
? theme.color.blue
|
||||||
: variant === CheckboxVariant.Primary
|
: variant === CheckboxVariant.Primary
|
||||||
? theme.border.color.inverted
|
? theme.border.color.inverted
|
||||||
: theme.border.color.secondaryInverted};
|
: 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: '';
|
content: '';
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 12px;
|
height: var(--size);
|
||||||
width: 12px;
|
width: var(--size);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:checked + label:before {
|
&:checked + label:before {
|
||||||
@ -64,12 +85,16 @@ const StyledInput = styled.input<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
& + label > svg {
|
& + label > svg {
|
||||||
height: 12px;
|
--padding: ${({ checkboxSize }) =>
|
||||||
left: 1px;
|
checkboxSize === CheckboxSize.Large ? '2px' : '1px'};
|
||||||
|
--size: ${({ checkboxSize }) =>
|
||||||
|
checkboxSize === CheckboxSize.Large ? '16px' : '12px'};
|
||||||
|
height: var(--size);
|
||||||
|
left: var(--padding);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
stroke: ${({ theme }) => theme.grayScale.gray0};
|
stroke: ${({ theme }) => theme.grayScale.gray0};
|
||||||
top: 1px;
|
top: var(--padding);
|
||||||
width: 12px;
|
width: var(--size);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -78,6 +103,8 @@ export function Checkbox({
|
|||||||
onChange,
|
onChange,
|
||||||
indeterminate,
|
indeterminate,
|
||||||
variant = CheckboxVariant.Primary,
|
variant = CheckboxVariant.Primary,
|
||||||
|
size = CheckboxSize.Small,
|
||||||
|
shape = CheckboxShape.Squared,
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const [isInternalChecked, setIsInternalChecked] =
|
const [isInternalChecked, setIsInternalChecked] =
|
||||||
React.useState<boolean>(false);
|
React.useState<boolean>(false);
|
||||||
@ -101,6 +128,8 @@ export function Checkbox({
|
|||||||
checked={isInternalChecked}
|
checked={isInternalChecked}
|
||||||
indeterminate={indeterminate}
|
indeterminate={indeterminate}
|
||||||
variant={variant}
|
variant={variant}
|
||||||
|
checkboxSize={size}
|
||||||
|
shape={shape}
|
||||||
onChange={(event) => handleChange(event.target.checked)}
|
onChange={(event) => handleChange(event.target.checked)}
|
||||||
/>
|
/>
|
||||||
<label htmlFor="checkbox">
|
<label htmlFor="checkbox">
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
|||||||
import { AutosizeTextInput } from '../AutosizeTextInput';
|
import { AutosizeTextInput } from '../AutosizeTextInput';
|
||||||
|
|
||||||
const meta: Meta<typeof AutosizeTextInput> = {
|
const meta: Meta<typeof AutosizeTextInput> = {
|
||||||
title: 'UI/Inputs/AutosizeTextInput',
|
title: 'UI/Input/AutosizeTextInput',
|
||||||
component: AutosizeTextInput,
|
component: AutosizeTextInput,
|
||||||
decorators: [ComponentDecorator],
|
decorators: [ComponentDecorator],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,74 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
|
import { CatalogDecorator } from '~/testing/decorators/CatalogDecorator';
|
||||||
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Checkbox,
|
||||||
|
CheckboxShape,
|
||||||
|
CheckboxSize,
|
||||||
|
CheckboxVariant,
|
||||||
|
} from '../Checkbox';
|
||||||
|
|
||||||
|
const meta: Meta<typeof Checkbox> = {
|
||||||
|
title: 'UI/Input/Checkbox',
|
||||||
|
component: Checkbox,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof Checkbox>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
checked: false,
|
||||||
|
indeterminate: false,
|
||||||
|
variant: CheckboxVariant.Primary,
|
||||||
|
size: CheckboxSize.Small,
|
||||||
|
shape: CheckboxShape.Squared,
|
||||||
|
},
|
||||||
|
decorators: [ComponentDecorator],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Catalog: Story = {
|
||||||
|
args: {},
|
||||||
|
argTypes: {
|
||||||
|
variant: { control: false },
|
||||||
|
size: { control: false },
|
||||||
|
indeterminate: { control: false },
|
||||||
|
checked: { control: false },
|
||||||
|
shape: { control: false },
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
catalog: [
|
||||||
|
{
|
||||||
|
name: 'state',
|
||||||
|
values: ['unchecked', 'checked', 'indeterminate'],
|
||||||
|
props: (state: string) => {
|
||||||
|
if (state === 'checked') {
|
||||||
|
return { checked: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state === 'indeterminate') {
|
||||||
|
return { indeterminate: true };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'shape',
|
||||||
|
values: Object.values(CheckboxShape),
|
||||||
|
props: (shape: CheckboxShape) => ({ shape }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'variant',
|
||||||
|
values: Object.values(CheckboxVariant),
|
||||||
|
props: (variant: CheckboxVariant) => ({ variant }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'size',
|
||||||
|
values: Object.values(CheckboxSize),
|
||||||
|
props: (size: CheckboxSize) => ({ size }),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
decorators: [CatalogDecorator],
|
||||||
|
};
|
||||||
@ -11,7 +11,7 @@ import { TextInput } from '../TextInput';
|
|||||||
const changeJestFn = jest.fn();
|
const changeJestFn = jest.fn();
|
||||||
|
|
||||||
const meta: Meta<typeof TextInput> = {
|
const meta: Meta<typeof TextInput> = {
|
||||||
title: 'UI/Inputs/TextInput',
|
title: 'UI/Input/TextInput',
|
||||||
component: TextInput,
|
component: TextInput,
|
||||||
decorators: [ComponentDecorator],
|
decorators: [ComponentDecorator],
|
||||||
args: { value: '', onChange: changeJestFn, placeholder: 'Placeholder' },
|
args: { value: '', onChange: changeJestFn, placeholder: 'Placeholder' },
|
||||||
|
|||||||
@ -12,6 +12,7 @@ const StyledOverflowingText = styled.div<{ cursorPointer: boolean }>`
|
|||||||
|
|
||||||
font-weight: inherit;
|
font-weight: inherit;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
text-decoration: inherit;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|||||||
Reference in New Issue
Block a user