From a287a2c27a8b874031c18e9a2bab85b80db72d89 Mon Sep 17 00:00:00 2001 From: DenisVorop Date: Mon, 10 Jul 2023 18:23:59 +0300 Subject: [PATCH 1/3] fix(GoalPreview): pin latest state comment --- package-lock.json | 48 +++------- package.json | 2 +- src/components/CommentFixed.tsx | 106 +++++++++++++++++++++ src/components/GoalPreview/GoalPreview.tsx | 25 ++++- 4 files changed, 146 insertions(+), 35 deletions(-) create mode 100644 src/components/CommentFixed.tsx diff --git a/package-lock.json b/package-lock.json index f2c43229d..422ce0ad1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@sentry/nextjs": "7.50.0", "@tanstack/react-query": "4.29.5", "@tanstack/react-query-devtools": "4.29.5", - "@taskany/bricks": "1.22.0", + "@taskany/bricks": "1.22.2", "@taskany/colors": "1.1.0", "@tippyjs/react": "4.2.6", "@trpc/client": "10.23.1", @@ -58,7 +58,6 @@ "@next/bundle-analyzer": "13.2.1", "@prisma/client": "4.16.2", "@svgr/webpack": "7.0.0", - "@taskany/bricks": "1.22.0", "@types/cors": "2.8.13", "@types/jest": "29.5.2", "@types/md5": "2.3.2", @@ -4718,7 +4717,6 @@ }, "node_modules/@monaco-editor/loader": { "version": "1.3.3", - "dev": true, "license": "MIT", "dependencies": { "state-local": "^1.0.6" @@ -4729,7 +4727,6 @@ }, "node_modules/@monaco-editor/react": { "version": "4.5.1", - "dev": true, "license": "MIT", "dependencies": { "@monaco-editor/loader": "^1.3.3" @@ -5823,9 +5820,9 @@ } }, "node_modules/@taskany/bricks": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/@taskany/bricks/-/bricks-1.22.0.tgz", - "integrity": "sha512-3nRHpavBIxFfa0z4a7U4bEarMdUZFURkEI3LNWkzyalzmoqGOs9f6DC1U2ElEuErHH/9jhfxMRM15ZboSCrjQA==", + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/@taskany/bricks/-/bricks-1.22.2.tgz", + "integrity": "sha512-i9kO8h6TbICIeGNMSh6R+ANMNhL2+55j6964lKPI4b9IzV6DUZ6tdzk6wty2XJedCdumZrvkHRNhTZT+9A9k2A==", "dependencies": { "@monaco-editor/react": "4.5.1", "@taskany/colors": "1.1.0", @@ -7157,7 +7154,6 @@ }, "node_modules/attr-accept": { "version": "2.2.2", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -9264,8 +9260,9 @@ }, "node_modules/eslint-config-next": { "version": "13.2.4", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.2.4.tgz", + "integrity": "sha512-lunIBhsoeqw6/Lfkd6zPt25w1bn0znLA/JCL+au1HoEpSb4/PpsOYsYtgV/q+YPsoKIOzFyU5xnb04iZnXjUvg==", "dev": true, - "license": "MIT", "dependencies": { "@next/eslint-plugin-next": "13.2.4", "@rushstack/eslint-patch": "^1.1.3", @@ -10076,7 +10073,6 @@ }, "node_modules/file-selector": { "version": "0.6.0", - "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.4.0" @@ -14893,7 +14889,6 @@ }, "node_modules/monaco-editor": { "version": "0.39.0", - "dev": true, "license": "MIT", "peer": true }, @@ -15787,7 +15782,6 @@ }, "node_modules/prop-types": { "version": "15.8.1", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -15797,7 +15791,6 @@ }, "node_modules/prop-types/node_modules/react-is": { "version": "16.13.1", - "dev": true, "license": "MIT" }, "node_modules/property-information": { @@ -15921,7 +15914,6 @@ }, "node_modules/react-dropzone": { "version": "14.2.3", - "dev": true, "license": "MIT", "dependencies": { "attr-accept": "^2.2.2", @@ -16933,7 +16925,6 @@ }, "node_modules/state-local": { "version": "1.0.7", - "dev": true, "license": "MIT" }, "node_modules/streamsearch": { @@ -17292,7 +17283,6 @@ }, "node_modules/teenyicons": { "version": "0.4.1", - "dev": true, "license": "MIT" }, "node_modules/terser": { @@ -21871,14 +21861,12 @@ }, "@monaco-editor/loader": { "version": "1.3.3", - "dev": true, "requires": { "state-local": "^1.0.6" } }, "@monaco-editor/react": { "version": "4.5.1", - "dev": true, "requires": { "@monaco-editor/loader": "^1.3.3" } @@ -22482,9 +22470,9 @@ } }, "@taskany/bricks": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/@taskany/bricks/-/bricks-1.22.0.tgz", - "integrity": "sha512-3nRHpavBIxFfa0z4a7U4bEarMdUZFURkEI3LNWkzyalzmoqGOs9f6DC1U2ElEuErHH/9jhfxMRM15ZboSCrjQA==", + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/@taskany/bricks/-/bricks-1.22.2.tgz", + "integrity": "sha512-i9kO8h6TbICIeGNMSh6R+ANMNhL2+55j6964lKPI4b9IzV6DUZ6tdzk6wty2XJedCdumZrvkHRNhTZT+9A9k2A==", "requires": { "@monaco-editor/react": "4.5.1", "@taskany/colors": "1.1.0", @@ -23439,8 +23427,7 @@ "dev": true }, "attr-accept": { - "version": "2.2.2", - "dev": true + "version": "2.2.2" }, "available-typed-arrays": { "version": "1.0.5", @@ -24828,6 +24815,8 @@ }, "eslint-config-next": { "version": "13.2.4", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.2.4.tgz", + "integrity": "sha512-lunIBhsoeqw6/Lfkd6zPt25w1bn0znLA/JCL+au1HoEpSb4/PpsOYsYtgV/q+YPsoKIOzFyU5xnb04iZnXjUvg==", "dev": true, "requires": { "@next/eslint-plugin-next": "13.2.4", @@ -25253,7 +25242,6 @@ }, "file-selector": { "version": "0.6.0", - "dev": true, "requires": { "tslib": "^2.4.0" } @@ -28173,7 +28161,6 @@ }, "monaco-editor": { "version": "0.39.0", - "dev": true, "peer": true }, "mri": { @@ -28683,7 +28670,6 @@ }, "prop-types": { "version": "15.8.1", - "dev": true, "requires": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -28691,8 +28677,7 @@ }, "dependencies": { "react-is": { - "version": "16.13.1", - "dev": true + "version": "16.13.1" } } }, @@ -28763,7 +28748,6 @@ }, "react-dropzone": { "version": "14.2.3", - "dev": true, "requires": { "attr-accept": "^2.2.2", "file-selector": "^0.6.0", @@ -29396,8 +29380,7 @@ } }, "state-local": { - "version": "1.0.7", - "dev": true + "version": "1.0.7" }, "streamsearch": { "version": "1.1.0" @@ -29612,8 +29595,7 @@ } }, "teenyicons": { - "version": "0.4.1", - "dev": true + "version": "0.4.1" }, "terser": { "version": "5.17.7", diff --git a/package.json b/package.json index 244fafa5f..bca2e93b3 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "@sentry/nextjs": "7.50.0", "@tanstack/react-query": "4.29.5", "@tanstack/react-query-devtools": "4.29.5", - "@taskany/bricks": "1.22.0", + "@taskany/bricks": "1.22.2", "@taskany/colors": "1.1.0", "@tippyjs/react": "4.2.6", "@trpc/client": "10.23.1", diff --git a/src/components/CommentFixed.tsx b/src/components/CommentFixed.tsx new file mode 100644 index 000000000..3d7e378f6 --- /dev/null +++ b/src/components/CommentFixed.tsx @@ -0,0 +1,106 @@ +import React, { FC, memo, useCallback, useState } from 'react'; +import styled from 'styled-components'; +import dynamic from 'next/dynamic'; +import { backgroundColor, gapM, gapS, gray4, gray9 } from '@taskany/colors'; +import { Card, CardComment, CardInfo, Link, PinAltIcon, StateDot, Text, nullable } from '@taskany/bricks'; +import { State, User } from '@prisma/client'; +import colorLayer from 'color-layer'; + +import { createLocaleDate } from '../utils/dateTime'; +import { usePageContext } from '../hooks/usePageContext'; +import { useLocale } from '../hooks/useLocale'; + +import { ActivityFeedItem } from './ActivityFeed'; +import { RelativeTime } from './RelativeTime/RelativeTime'; +import { Circle, CircledIcon } from './Circle'; + +const Md = dynamic(() => import('./Md')); + +const StyledCommentCard = styled(Card)` + position: relative; + min-height: 60px; + + transition: border-color 200ms ease-in-out; + + &::before { + position: absolute; + z-index: 0; + + content: ''; + + width: 14px; + height: 14px; + + background-color: ${gray4}; + + border-left: 1px solid ${gray4}; + border-top: 1px solid ${gray4}; + border-radius: 2px; + + transform: rotate(-45deg); + transition: border-color 200ms ease-in-out; + + top: 8px; + left: -6px; + } +`; + +const StyledStateDot = styled(StateDot)` + margin-right: ${gapS}; +`; + +const StyledTimestamp = styled.div` + display: flex; + align-items: center; + + padding-bottom: ${gapM}; +`; + +interface CommentFixedProps { + id: string; + author?: User | null; + description: string; + createdAt: Date; + state?: State | null; +} + +export const CommentFixed: FC = memo(({ id, author, description, createdAt, state }) => { + const { themeId } = usePageContext(); + const locale = useLocale(); + const [isRelativeTime, setIsRelativeTime] = useState(true); + + const onChangeTypeDate = useCallback((e: React.MouseEvent | undefined) => { + if (e && e.target === e.currentTarget) { + setIsRelativeTime((prev) => !prev); + } + }, []); + + return ( + + + + + + + + {author?.name} —{' '} + + + + + + + {nullable(state, (s) => ( + + + + {createLocaleDate(createdAt, { locale })} + + + ))} + {description} + + + + ); +}); diff --git a/src/components/GoalPreview/GoalPreview.tsx b/src/components/GoalPreview/GoalPreview.tsx index 00f7498a5..c90177563 100644 --- a/src/components/GoalPreview/GoalPreview.tsx +++ b/src/components/GoalPreview/GoalPreview.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useRef, useState } from 'react'; +import React, { useCallback, useMemo, useRef, useState } from 'react'; import dynamic from 'next/dynamic'; import styled from 'styled-components'; import { danger0, gapM, gapS, gray7 } from '@taskany/colors'; @@ -43,6 +43,7 @@ import { State } from '../State'; import { useGoalDependencyResource } from '../../hooks/useGoalDependencyResource'; import { GoalDependencyAddForm } from '../GoalDependencyForm/GoalDependencyForm'; import { GoalDependencyListByKind } from '../GoalDependencyList/GoalDependencyList'; +import { CommentFixed } from '../CommentFixed'; import { tr } from './GoalPreview.i18n'; @@ -95,6 +96,8 @@ const StyledModalContent = styled(ModalContent)` const StyledCard = styled(Card)` min-height: 60px; + + margin-bottom: ${gapS}; `; const GoalPreview: React.FC = ({ preview, onClose, onDelete }) => { @@ -188,6 +191,7 @@ const GoalPreview: React.FC = ({ preview, onClose, onDelete }) const commentsRef = useRef(null); const contentRef = useRef(null); const headerRef = useRef(null); + const onCommentsClick = useCallback(() => { commentsRef.current && contentRef.current && @@ -200,6 +204,15 @@ const GoalPreview: React.FC = ({ preview, onClose, onDelete }) const { title, description, updatedAt } = goal || preview; + const lastChangedStatusComment = useMemo(() => { + if (!goal || goal.comments.length <= 1) { + return null; + } + + return goal.comments.findLast((comment) => comment.stateId); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [goal?.comments]); + return ( <> @@ -305,6 +318,16 @@ const GoalPreview: React.FC = ({ preview, onClose, onDelete }) + {nullable(lastChangedStatusComment, (value) => ( + + ))} + {nullable(goal, ({ activityFeed, id, goalAchiveCriteria, relations, project, _isEditable }) => ( Date: Thu, 13 Jul 2023 09:40:18 +0300 Subject: [PATCH 2/3] fix(CommentView): remove duplicate component --- src/components/CommentFixed.tsx | 106 ------------------ src/components/CommentView/CommentView.tsx | 121 ++++++++++++--------- src/components/GoalPreview/GoalPreview.tsx | 14 ++- 3 files changed, 78 insertions(+), 163 deletions(-) delete mode 100644 src/components/CommentFixed.tsx diff --git a/src/components/CommentFixed.tsx b/src/components/CommentFixed.tsx deleted file mode 100644 index 3d7e378f6..000000000 --- a/src/components/CommentFixed.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import React, { FC, memo, useCallback, useState } from 'react'; -import styled from 'styled-components'; -import dynamic from 'next/dynamic'; -import { backgroundColor, gapM, gapS, gray4, gray9 } from '@taskany/colors'; -import { Card, CardComment, CardInfo, Link, PinAltIcon, StateDot, Text, nullable } from '@taskany/bricks'; -import { State, User } from '@prisma/client'; -import colorLayer from 'color-layer'; - -import { createLocaleDate } from '../utils/dateTime'; -import { usePageContext } from '../hooks/usePageContext'; -import { useLocale } from '../hooks/useLocale'; - -import { ActivityFeedItem } from './ActivityFeed'; -import { RelativeTime } from './RelativeTime/RelativeTime'; -import { Circle, CircledIcon } from './Circle'; - -const Md = dynamic(() => import('./Md')); - -const StyledCommentCard = styled(Card)` - position: relative; - min-height: 60px; - - transition: border-color 200ms ease-in-out; - - &::before { - position: absolute; - z-index: 0; - - content: ''; - - width: 14px; - height: 14px; - - background-color: ${gray4}; - - border-left: 1px solid ${gray4}; - border-top: 1px solid ${gray4}; - border-radius: 2px; - - transform: rotate(-45deg); - transition: border-color 200ms ease-in-out; - - top: 8px; - left: -6px; - } -`; - -const StyledStateDot = styled(StateDot)` - margin-right: ${gapS}; -`; - -const StyledTimestamp = styled.div` - display: flex; - align-items: center; - - padding-bottom: ${gapM}; -`; - -interface CommentFixedProps { - id: string; - author?: User | null; - description: string; - createdAt: Date; - state?: State | null; -} - -export const CommentFixed: FC = memo(({ id, author, description, createdAt, state }) => { - const { themeId } = usePageContext(); - const locale = useLocale(); - const [isRelativeTime, setIsRelativeTime] = useState(true); - - const onChangeTypeDate = useCallback((e: React.MouseEvent | undefined) => { - if (e && e.target === e.currentTarget) { - setIsRelativeTime((prev) => !prev); - } - }, []); - - return ( - - - - - - - - {author?.name} —{' '} - - - - - - - {nullable(state, (s) => ( - - - - {createLocaleDate(createdAt, { locale })} - - - ))} - {description} - - - - ); -}); diff --git a/src/components/CommentView/CommentView.tsx b/src/components/CommentView/CommentView.tsx index 90de138c9..e9f4e6feb 100644 --- a/src/components/CommentView/CommentView.tsx +++ b/src/components/CommentView/CommentView.tsx @@ -1,7 +1,7 @@ -import React, { FC, useCallback, useState } from 'react'; +import React, { FC, useCallback, useMemo, useState } from 'react'; import styled from 'styled-components'; import dynamic from 'next/dynamic'; -import { brandColor, danger0, gapM, gapS, gray4, gray9 } from '@taskany/colors'; +import { brandColor, danger0, gapM, gapS, gray4, gray9, backgroundColor } from '@taskany/colors'; import { BinIcon, Card, @@ -16,6 +16,7 @@ import { Text, UserPic, nullable, + PinAltIcon, } from '@taskany/bricks'; import { Reaction, State, User } from '@prisma/client'; import colorLayer from 'color-layer'; @@ -23,11 +24,12 @@ import colorLayer from 'color-layer'; import { useReactionsResource } from '../../hooks/useReactionsResource'; import { useCommentResource } from '../../hooks/useCommentResource'; import { usePageContext } from '../../hooks/usePageContext'; +import { useLocale } from '../../hooks/useLocale'; import { createLocaleDate } from '../../utils/dateTime'; import { Reactions } from '../Reactions'; import { ActivityFeedItem } from '../ActivityFeed'; -import { useLocale } from '../../hooks/useLocale'; import { RelativeTime } from '../RelativeTime/RelativeTime'; +import { Circle, CircledIcon } from '../Circle'; import { tr } from './CommentView.i18n'; @@ -45,6 +47,7 @@ interface CommentViewProps { isNew?: boolean; isEditable?: boolean; state?: State | null; + isPinned?: boolean; onReactionToggle?: React.ComponentProps['onClick']; onDelete?: (id: string) => void; @@ -104,25 +107,48 @@ const StyledCommentCard = styled(Card)<{ isNew?: boolean }>` `; const StyledCardInfo = styled(CardInfo)` - display: grid; - grid-template-columns: 6fr 6fr; -`; - -const StyledReactions = styled.div` - padding-top: ${gapM}; + display: flex; + justify-content: space-between; `; -const StyledStateDot = styled(StateDot)` - margin-right: ${gapS}; +const StyledCardComment = styled(CardComment)` + display: flex; + flex-direction: column; + gap: ${gapM}; `; const StyledTimestamp = styled.div` display: flex; align-items: center; - - padding-bottom: ${gapM}; + gap: ${gapS}; `; +const renderTriggerHelper = ({ ref, onClick }: { ref: React.RefObject; onClick: () => void }) => ( + +); + +const renderItemHelper = ({ item, cursor, index }: { item: any; cursor: number; index: number }) => ( + + {item.label} + +); + +const iconRenderCondition = (isPinned: boolean, image?: User['image'], email?: User['email']) => + isPinned ? ( + + + + ) : ( + + ); + export const CommentView: FC = ({ id, author, @@ -134,6 +160,7 @@ export const CommentView: FC = ({ state, onDelete, onReactionToggle, + isPinned = false, }) => { const { themeId } = usePageContext(); const locale = useLocale(); @@ -181,9 +208,26 @@ export const CommentView: FC = ({ })({ id }); }, [id, onDelete, remove]); + const dropdownItems = useMemo( + () => [ + { + label: tr('Edit'), + icon: , + onClick: onEditClick, + }, + { + label: tr('Delete'), + color: danger0, + icon: , + onClick: onDeleteClick, + }, + ], + [onDeleteClick, onEditClick], + ); + return ( - - + + {iconRenderCondition(isPinned, author?.image, author?.email)} {editMode ? ( = ({ ))} {nullable(isEditable, () => ( - - , - onClick: onEditClick, - }, - { - label: tr('Delete'), - color: danger0, - icon: , - onClick: onDeleteClick, - }, - ]} - renderTrigger={({ ref, onClick }) => ( - - )} - renderItem={({ item, cursor, index }) => ( - - {item.label} - - )} - /> - + ))} - + {nullable(state, (s) => ( - + {createLocaleDate(createdAt, { locale })} @@ -256,11 +273,9 @@ export const CommentView: FC = ({ {commentDescription} {nullable(reactions?.length, () => ( - - - + ))} - + )} diff --git a/src/components/GoalPreview/GoalPreview.tsx b/src/components/GoalPreview/GoalPreview.tsx index c90177563..c131ccc3e 100644 --- a/src/components/GoalPreview/GoalPreview.tsx +++ b/src/components/GoalPreview/GoalPreview.tsx @@ -43,7 +43,7 @@ import { State } from '../State'; import { useGoalDependencyResource } from '../../hooks/useGoalDependencyResource'; import { GoalDependencyAddForm } from '../GoalDependencyForm/GoalDependencyForm'; import { GoalDependencyListByKind } from '../GoalDependencyList/GoalDependencyList'; -import { CommentFixed } from '../CommentFixed'; +import { CommentView } from '../CommentView/CommentView'; import { tr } from './GoalPreview.i18n'; @@ -209,9 +209,10 @@ const GoalPreview: React.FC = ({ preview, onClose, onDelete }) return null; } - return goal.comments.findLast((comment) => comment.stateId); + const foundResult = goal.comments.findLast((comment) => comment.stateId); + return foundResult?.stateId === goal.stateId ? foundResult : null; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [goal?.comments]); + }, [goal?.comments, goal?.stateId]); return ( <> @@ -319,12 +320,17 @@ const GoalPreview: React.FC = ({ preview, onClose, onDelete }) {nullable(lastChangedStatusComment, (value) => ( - ))} From b95fd68b9d9de64923abf56702ccfb66a4bfc2d9 Mon Sep 17 00:00:00 2001 From: DenisVorop Date: Fri, 14 Jul 2023 18:25:01 +0300 Subject: [PATCH 3/3] fix: try merge --- src/components/GoalPreview/GoalPreview.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/GoalPreview/GoalPreview.tsx b/src/components/GoalPreview/GoalPreview.tsx index c131ccc3e..94bd5dace 100644 --- a/src/components/GoalPreview/GoalPreview.tsx +++ b/src/components/GoalPreview/GoalPreview.tsx @@ -205,7 +205,7 @@ const GoalPreview: React.FC = ({ preview, onClose, onDelete }) const { title, description, updatedAt } = goal || preview; const lastChangedStatusComment = useMemo(() => { - if (!goal || goal.comments.length <= 1) { + if (!goal || goal.comments?.length <= 1) { return null; }