From 3fae0afdcfcb8d4f7e959f9a9b935dcda0a4e53b Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 20 Nov 2025 01:02:48 +0100 Subject: [PATCH 1/6] feat: likes and dislikes on run items and comments --- .../lib/assets/icons/icon-thumb-up-filled.svg | 3 + .../src/lib/assets/icons/icon-thumb-up.svg | 3 + .../assets/icons/icon-thumbs-down-filled.svg | 3 + .../src/lib/assets/icons/icon-thumbs-down.svg | 3 + src/apps/review/src/lib/assets/icons/index.ts | 8 + .../AiFeedback/AiFeedback.module.scss | 45 ++++ .../AiFeedback/AiFeedback.tsx | 15 +- .../AiFeedbackActions.module.scss | 40 +++ .../AiFeedbackActions/AiFeedbackActions.tsx | 227 ++++++++++++++++++ .../AiFeedbackComments.module.scss | 29 +++ .../AiFeedbackComments/AiFeedbackComments.tsx | 57 +++++ .../src/lib/services/scorecards.service.ts | 29 ++- 12 files changed, 459 insertions(+), 3 deletions(-) create mode 100644 src/apps/review/src/lib/assets/icons/icon-thumb-up-filled.svg create mode 100644 src/apps/review/src/lib/assets/icons/icon-thumb-up.svg create mode 100644 src/apps/review/src/lib/assets/icons/icon-thumbs-down-filled.svg create mode 100644 src/apps/review/src/lib/assets/icons/icon-thumbs-down.svg create mode 100644 src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackActions/AiFeedbackActions.module.scss create mode 100644 src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackActions/AiFeedbackActions.tsx create mode 100644 src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackComments/AiFeedbackComments.module.scss create mode 100644 src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackComments/AiFeedbackComments.tsx diff --git a/src/apps/review/src/lib/assets/icons/icon-thumb-up-filled.svg b/src/apps/review/src/lib/assets/icons/icon-thumb-up-filled.svg new file mode 100644 index 000000000..e862a480c --- /dev/null +++ b/src/apps/review/src/lib/assets/icons/icon-thumb-up-filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/apps/review/src/lib/assets/icons/icon-thumb-up.svg b/src/apps/review/src/lib/assets/icons/icon-thumb-up.svg new file mode 100644 index 000000000..8f31f2966 --- /dev/null +++ b/src/apps/review/src/lib/assets/icons/icon-thumb-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/apps/review/src/lib/assets/icons/icon-thumbs-down-filled.svg b/src/apps/review/src/lib/assets/icons/icon-thumbs-down-filled.svg new file mode 100644 index 000000000..5f8c2c03f --- /dev/null +++ b/src/apps/review/src/lib/assets/icons/icon-thumbs-down-filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/apps/review/src/lib/assets/icons/icon-thumbs-down.svg b/src/apps/review/src/lib/assets/icons/icon-thumbs-down.svg new file mode 100644 index 000000000..abe7616a6 --- /dev/null +++ b/src/apps/review/src/lib/assets/icons/icon-thumbs-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/apps/review/src/lib/assets/icons/index.ts b/src/apps/review/src/lib/assets/icons/index.ts index dffc1a502..de1e549c0 100644 --- a/src/apps/review/src/lib/assets/icons/index.ts +++ b/src/apps/review/src/lib/assets/icons/index.ts @@ -15,6 +15,10 @@ import { ReactComponent as IconPremium } from './icon-premium.svg' import { ReactComponent as IconComment } from './icon-comment.svg' import { ReactComponent as IconEdit } from './icon-edit.svg' import { ReactComponent as IconFile } from './icon-file.svg' +import { ReactComponent as IconThumbsUp } from './icon-thumb-up.svg' +import { ReactComponent as IconThumbsDown } from './icon-thumbs-down.svg' +import { ReactComponent as IconThumbsUpFilled } from './icon-thumb-up-filled.svg' +import { ReactComponent as IconThumbsDownFilled } from './icon-thumbs-down-filled.svg' export * from './editor/bold' export * from './editor/code' @@ -49,6 +53,10 @@ export { IconComment, IconEdit, IconFile, + IconThumbsUp, + IconThumbsDown, + IconThumbsUpFilled, + IconThumbsDownFilled, } export const phasesIcons = { diff --git a/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedback/AiFeedback.module.scss b/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedback/AiFeedback.module.scss index fe1718d67..bd3e71bc7 100644 --- a/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedback/AiFeedback.module.scss +++ b/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedback/AiFeedback.module.scss @@ -8,4 +8,49 @@ margin-bottom: $sp-2; } } + + .comment { + margin-top: $sp-3; + padding-left: $sp-4; + border-left: 2px solid black; + } + + .commentContent { + margin-bottom: $sp-1; + } + + .commentActions { + display: flex; + gap: $sp-2; + margin-bottom: $sp-2; + } + + .commentBtn { + display: inline-flex; + align-items: center; + gap: $sp-1; + background: transparent; + border: none; + cursor: pointer; + padding: $sp-1; + } + + .replies { + margin-left: $sp-4; + margin-top: $sp-2; + } + + .reply { + margin-top: $sp-2; + } + + .count { + font-size: 12px; + margin-left: $sp-1; + } + + .active { + color: blue; + font-weight: 600; + } } diff --git a/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedback/AiFeedback.tsx b/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedback/AiFeedback.tsx index 5b57e6f8f..2e1007dd5 100644 --- a/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedback/AiFeedback.tsx +++ b/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedback/AiFeedback.tsx @@ -7,6 +7,8 @@ import { ScorecardViewerContextValue, useScorecardViewerContext } from '../../Sc import { ScorecardQuestionRow } from '../ScorecardQuestionRow' import { ScorecardScore } from '../../ScorecardScore' import { MarkdownReview } from '../../../../MarkdownReview' +import { AiFeedbackActions } from '../AiFeedbackActions/AiFeedbackActions' +import { AiFeedbackComments } from '../AiFeedbackComments/AiFeedbackComments' import styles from './AiFeedback.module.scss' @@ -16,10 +18,12 @@ interface AiFeedbackProps { const AiFeedback: FC = props => { const { aiFeedbackItems, scoreMap }: ScorecardViewerContextValue = useScorecardViewerContext() - const feedback = useMemo(() => ( - aiFeedbackItems?.find(r => r.scorecardQuestionId === props.question.id) + const feedback: any = useMemo(() => ( + aiFeedbackItems?.find((r: any) => r.scorecardQuestionId === props.question.id) ), [props.question.id, aiFeedbackItems]) + const commentsArr: any[] = (feedback?.comments) || [] + if (!aiFeedbackItems?.length || !feedback) { return <> } @@ -43,7 +47,14 @@ const AiFeedback: FC = props => { {feedback.questionScore ? 'Yes' : 'No'}

)} + + + + + {commentsArr.length > 0 && ( + + )} ) } diff --git a/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackActions/AiFeedbackActions.module.scss b/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackActions/AiFeedbackActions.module.scss new file mode 100644 index 000000000..ba7b4fcec --- /dev/null +++ b/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackActions/AiFeedbackActions.module.scss @@ -0,0 +1,40 @@ +@import '@libs/ui/styles/includes'; + +.actions { + display: flex; + gap: $sp-2; + margin-top: $sp-3; + .count { + font-family: $font-roboto; + color: #0D61BF; + font-weight: 700; + font-size: 14px; + } +} + +.actionBtn { + display: inline-flex; + align-items: center; + gap: $sp-2; + background: transparent; + border: none; + cursor: pointer; + color: black; + padding: $sp-1 $sp-2; + &.active { + svg { + path { + fill: $link-blue-dark; + } + } + } + + svg { + width: 20px; + height: 20px; + } + + &:hover { + opacity: 0.85; + } +} \ No newline at end of file diff --git a/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackActions/AiFeedbackActions.tsx b/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackActions/AiFeedbackActions.tsx new file mode 100644 index 000000000..d7e71567b --- /dev/null +++ b/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackActions/AiFeedbackActions.tsx @@ -0,0 +1,227 @@ +/* eslint-disable react/jsx-no-bind */ +import { FC, useCallback, useContext, useEffect, useState } from 'react' +import { mutate } from 'swr' + +import { + IconThumbsDown, + IconThumbsDownFilled, + IconThumbsUp, + IconThumbsUpFilled, +} from '~/apps/review/src/lib/assets/icons' +import { ReviewAppContext } from '~/apps/review/src/lib/contexts' +import { useReviewsContext } from '~/apps/review/src/pages/reviews/ReviewsContext' +import { updateLikesOrDislikesOnRunItem, updateLikesOrDislikesOnRunItemComment } from '~/apps/review/src/lib/services' +import { EnvironmentConfig } from '~/config' +import { ReviewAppContextModel, ReviewsContextModel } from '~/apps/review/src/lib/models' + +import { AiFeedbackComment } from '../AiFeedbackComments/AiFeedbackComments' + +import styles from './AiFeedbackActions.module.scss' + +export enum VOTE_TYPE { + UPVOTE = 'UPVOTE', + DOWNVOTE = 'DOWNVOTE' +} + +interface AiFeedbackActionsProps { + actionType: 'comment' | 'runItem' + comment?: AiFeedbackComment + feedback?: any +} + +export const AiFeedbackActions: FC = props => { + + const [userVote, setUserVote] = useState(undefined) + const [upVotes, setUpVotes] = useState(0) + const [downVotes, setDownVotes] = useState(0) + + const { loginUserInfo }: ReviewAppContextModel = useContext(ReviewAppContext) + const { workflowId, workflowRun }: ReviewsContextModel = useReviewsContext() + + const votesArr: any[] = (props.actionType === 'runItem' ? (props.feedback?.votes) : (props.comment?.votes)) || [] + + const setInitialVotesForFeedback = (): void => { + const initialUp = props.feedback?.upVotes ?? votesArr.filter(v => String(v.voteType) + .toLowerCase() + .includes('up')).length + const initialDown = props.feedback?.downVotes ?? votesArr.filter(v => String(v.voteType) + .toLowerCase() + .includes('down')).length + + const myVote = votesArr.find(v => String(v.createdBy) === String(loginUserInfo?.userId)) + setUpVotes(initialUp) + setDownVotes(initialDown) + setUserVote(myVote?.voteType ?? undefined) + } + + const setInitialVotesForComment = (): void => { + const initialUp = votesArr.filter(v => String(v.voteType) + .toLowerCase() + .includes('up')).length + const initialDown = votesArr.filter(v => String(v.voteType) + .toLowerCase() + .includes('down')).length + + const myVote = votesArr.find(v => String(v.createdBy) === String(loginUserInfo?.userId)) + setUpVotes(initialUp) + setDownVotes(initialDown) + setUserVote(myVote?.voteType ?? undefined) + } + + useEffect(() => { + if (props.actionType === 'runItem') { + setInitialVotesForFeedback() + } else { + setInitialVotesForComment() + } + }, [props.actionType, props.feedback?.id, votesArr.length, loginUserInfo?.userId]) + + const voteOnItem = useCallback(async (type: VOTE_TYPE) => { + if (!workflowId || !workflowRun?.id) return + const current = userVote + let up = false + let down = false + + if (current === type) { + // remove vote + up = false + down = false + } else if (!current) { + up = type === VOTE_TYPE.UPVOTE + down = type === VOTE_TYPE.DOWNVOTE + } else { + // switch vote + up = type === VOTE_TYPE.UPVOTE + down = type === VOTE_TYPE.DOWNVOTE + } + + // optimistic update + const prevUserVote = userVote + const prevUp = upVotes + const prevDown = downVotes + + if (current === type) { + // removing + if (type === VOTE_TYPE.UPVOTE) setUpVotes(Math.max(0, upVotes - 1)) + else setDownVotes(Math.max(0, downVotes - 1)) + setUserVote(undefined) + } else if (!current) { + if (type === VOTE_TYPE.UPVOTE) setUpVotes(upVotes + 1) + else setDownVotes(downVotes + 1) + setUserVote(type) + } else { + // switch + if (type === VOTE_TYPE.UPVOTE) { + setUpVotes(upVotes + 1) + setDownVotes(Math.max(0, downVotes - 1)) + } else { + setDownVotes(downVotes + 1) + setUpVotes(Math.max(0, upVotes - 1)) + } + + setUserVote(type) + } + + try { + await updateLikesOrDislikesOnRunItem(workflowId, workflowRun.id, props.feedback.id, { + downVote: down, + upVote: up, + }) + } catch (err) { + // rollback + setUserVote(prevUserVote) + setUpVotes(prevUp) + setDownVotes(prevDown) + } + }, [workflowId, workflowRun, props.feedback?.id, userVote, upVotes, downVotes]) + + const voteOnComment = useCallback(async (c: any, type: VOTE_TYPE) => { + if (!workflowId || !workflowRun?.id) return + const votes = (c.votes || []) + const my = votes.find((v: any) => String(v.createdBy) === String(loginUserInfo?.userId)) + const current = my?.voteType ?? undefined + + let up = false + let down = false + + if (current === type) { + up = false + down = false + } else if (!current) { + up = type === VOTE_TYPE.UPVOTE + down = type === VOTE_TYPE.DOWNVOTE + } else { + up = type === VOTE_TYPE.UPVOTE + down = type === VOTE_TYPE.DOWNVOTE + } + + const prevUserVote = userVote + const prevUp = upVotes + const prevDown = downVotes + + if (current === type) { + // removing + if (type === VOTE_TYPE.UPVOTE) setUpVotes(Math.max(0, upVotes - 1)) + else setDownVotes(Math.max(0, downVotes - 1)) + setUserVote(undefined) + } else if (!current) { + if (type === VOTE_TYPE.UPVOTE) setUpVotes(upVotes + 1) + else setDownVotes(downVotes + 1) + setUserVote(type) + } else { + // switch + if (type === VOTE_TYPE.UPVOTE) { + setUpVotes(upVotes + 1) + setDownVotes(Math.max(0, downVotes - 1)) + } else { + setDownVotes(downVotes + 1) + setUpVotes(Math.max(0, upVotes - 1)) + } + + setUserVote(type) + + } + + try { + await updateLikesOrDislikesOnRunItemComment(workflowId, workflowRun.id, props.feedback.id, c.id, { + downVote: down, + upVote: up, + }) + await mutate(`${EnvironmentConfig.API.V6}/workflows/${workflowId}/runs/${workflowRun.id}/items`) + } catch (err) { + setUserVote(prevUserVote) + setUpVotes(prevUp) + setDownVotes(prevDown) + } + }, [workflowId, workflowRun, props.feedback?.id, loginUserInfo]) + + const onVote = (action: VOTE_TYPE): void => { + if (props.actionType === 'comment') { + voteOnComment(props.comment as AiFeedbackComment, action) + } else { + voteOnItem(action) + } + } + + return ( +
+ + + +
+ ) +} diff --git a/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackComments/AiFeedbackComments.module.scss b/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackComments/AiFeedbackComments.module.scss new file mode 100644 index 000000000..1a465a9b1 --- /dev/null +++ b/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackComments/AiFeedbackComments.module.scss @@ -0,0 +1,29 @@ +@import '@libs/ui/styles/includes'; + +.comments { + font-family: "Nunito Sans", sans-serif; + .comment { + margin-top: 32px; + background-color: #E0E4E8; + padding: 16px; + .info { + margin: 16px 0; + font-size: 14px; + .reply { + color: #0A0A0A; + font-weight: $font-weight-bold; + } + .text { + font-weight: $font-weight-normal; + color: #767676; + } + .name { + color: #0A0A0A; + font-weight: $font-weight-bold; + } + .date { + color: #0A0A0A; + } + } + } +} \ No newline at end of file diff --git a/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackComments/AiFeedbackComments.tsx b/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackComments/AiFeedbackComments.tsx new file mode 100644 index 000000000..28c9a1414 --- /dev/null +++ b/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackComments/AiFeedbackComments.tsx @@ -0,0 +1,57 @@ +import { FC } from 'react' + +import { AiFeedbackActions } from '../AiFeedbackActions/AiFeedbackActions' + +import styles from './AiFeedbackComments.module.scss' + +export interface AiFeedbackVote { + id: string + parentId: string + voteType: string + createdAt: string + createdBy: string +} +export interface AiFeedbackComment { + id: string + content: string + parentId: string + createdBy: string + createdAt: string + createdUser: { + userId: string + handle: string + ratingColor: string + } + votes: AiFeedbackVote[] +} + +interface AiFeedbackCommentsProps { + comments: AiFeedbackComment[] + feedback: any +} + +export const AiFeedbackComments: FC = props => ( +
+ {props.comments.filter(c => !c.parentId) + .map((comment: AiFeedbackComment) => ( +
+
+ Reply + by + + {comment.createdUser.handle} + + on + {comment.createdAt} +
+
{comment.content}
+ +
+ ))} +
+) diff --git a/src/apps/review/src/lib/services/scorecards.service.ts b/src/apps/review/src/lib/services/scorecards.service.ts index 909527b3d..6ff09fb73 100644 --- a/src/apps/review/src/lib/services/scorecards.service.ts +++ b/src/apps/review/src/lib/services/scorecards.service.ts @@ -1,7 +1,7 @@ /** * Scorecards service */ -import { xhrPostAsync, xhrPutAsync } from '~/libs/core' +import { xhrPatchAsync, xhrPostAsync, xhrPutAsync } from '~/libs/core' import { EnvironmentConfig } from '~/config' import { Scorecard } from '../models' @@ -29,3 +29,30 @@ export const saveScorecard = async (scorecard: Scorecard): Promise => return xhrPutAsync(`${baseUrl}/${scorecard.id}`, scorecard) } + +export const updateLikesOrDislikesOnRunItem = ( + workflowId: string, + runId: string, + feedbackId: string, + body: { + upVote: boolean + downVote: boolean + }, +): Promise => xhrPatchAsync( + `${EnvironmentConfig.API.V6}/workflows/${workflowId}/runs/${runId}/items/${feedbackId}`, + body, +) + +export const updateLikesOrDislikesOnRunItemComment = ( + workflowId: string, + runId: string, + feedbackId: string, + commentId: string, + body: { + upVote: boolean + downVote: boolean + }, +): Promise => xhrPatchAsync( + `${EnvironmentConfig.API.V6}/workflows/${workflowId}/runs/${runId}/items/${feedbackId}/comments/${commentId}`, + body, +) From d383431df84accd261832fee29f004a324581ba5 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 20 Nov 2025 01:10:37 +0100 Subject: [PATCH 2/6] feat: likes and dislikes on run items and comments --- .../AiFeedbackComments/AiFeedbackComments.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackComments/AiFeedbackComments.tsx b/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackComments/AiFeedbackComments.tsx index 28c9a1414..5916279fe 100644 --- a/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackComments/AiFeedbackComments.tsx +++ b/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackComments/AiFeedbackComments.tsx @@ -1,4 +1,5 @@ import { FC } from 'react' +import moment from 'moment' import { AiFeedbackActions } from '../AiFeedbackActions/AiFeedbackActions' @@ -47,7 +48,7 @@ export const AiFeedbackComments: FC = props => ( {comment.createdUser.handle} on - {comment.createdAt} + { moment(comment.createdAt).local().format('MMM DD, hh:mm A')}
{comment.content}
From 5dd1dc499f46491f862ddeb394ab5093614c842c Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Thu, 20 Nov 2025 01:11:50 +0100 Subject: [PATCH 3/6] feat: likes and dislikes on run items and comments --- .../AiFeedbackComments/AiFeedbackComments.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackComments/AiFeedbackComments.tsx b/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackComments/AiFeedbackComments.tsx index 5916279fe..46bb0ec91 100644 --- a/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackComments/AiFeedbackComments.tsx +++ b/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackComments/AiFeedbackComments.tsx @@ -48,7 +48,11 @@ export const AiFeedbackComments: FC = props => ( {comment.createdUser.handle} on - { moment(comment.createdAt).local().format('MMM DD, hh:mm A')} + + { moment(comment.createdAt) + .local() + .format('MMM DD, hh:mm A')} +
{comment.content}
From 564381fe71e188a5121ba7998b6006d02113a8f3 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Fri, 21 Nov 2025 00:24:57 +0100 Subject: [PATCH 4/6] fix: moved setInitialVotesForFeedback into useEffect --- .../AiFeedbackActions/AiFeedbackActions.tsx | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackActions/AiFeedbackActions.tsx b/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackActions/AiFeedbackActions.tsx index d7e71567b..306338459 100644 --- a/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackActions/AiFeedbackActions.tsx +++ b/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackActions/AiFeedbackActions.tsx @@ -40,35 +40,35 @@ export const AiFeedbackActions: FC = props => { const votesArr: any[] = (props.actionType === 'runItem' ? (props.feedback?.votes) : (props.comment?.votes)) || [] - const setInitialVotesForFeedback = (): void => { - const initialUp = props.feedback?.upVotes ?? votesArr.filter(v => String(v.voteType) - .toLowerCase() - .includes('up')).length - const initialDown = props.feedback?.downVotes ?? votesArr.filter(v => String(v.voteType) - .toLowerCase() - .includes('down')).length - - const myVote = votesArr.find(v => String(v.createdBy) === String(loginUserInfo?.userId)) - setUpVotes(initialUp) - setDownVotes(initialDown) - setUserVote(myVote?.voteType ?? undefined) - } - - const setInitialVotesForComment = (): void => { - const initialUp = votesArr.filter(v => String(v.voteType) - .toLowerCase() - .includes('up')).length - const initialDown = votesArr.filter(v => String(v.voteType) - .toLowerCase() - .includes('down')).length - - const myVote = votesArr.find(v => String(v.createdBy) === String(loginUserInfo?.userId)) - setUpVotes(initialUp) - setDownVotes(initialDown) - setUserVote(myVote?.voteType ?? undefined) - } - useEffect(() => { + const setInitialVotesForFeedback = (): void => { + const initialUp = props.feedback?.upVotes ?? votesArr.filter(v => String(v.voteType) + .toLowerCase() + .includes('up')).length + const initialDown = props.feedback?.downVotes ?? votesArr.filter(v => String(v.voteType) + .toLowerCase() + .includes('down')).length + + const myVote = votesArr.find(v => String(v.createdBy) === String(loginUserInfo?.userId)) + setUpVotes(initialUp) + setDownVotes(initialDown) + setUserVote(myVote?.voteType ?? undefined) + } + + const setInitialVotesForComment = (): void => { + const initialUp = votesArr.filter(v => String(v.voteType) + .toLowerCase() + .includes('up')).length + const initialDown = votesArr.filter(v => String(v.voteType) + .toLowerCase() + .includes('down')).length + + const myVote = votesArr.find(v => String(v.createdBy) === String(loginUserInfo?.userId)) + setUpVotes(initialUp) + setDownVotes(initialDown) + setUserVote(myVote?.voteType ?? undefined) + } + if (props.actionType === 'runItem') { setInitialVotesForFeedback() } else { From 79091296716af3800e4a123c13ab29bb3e345072 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Fri, 21 Nov 2025 00:27:27 +0100 Subject: [PATCH 5/6] fix: moved setInitialVotesForFeedback into useEffect --- .../AiFeedbackActions/AiFeedbackActions.tsx | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackActions/AiFeedbackActions.tsx b/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackActions/AiFeedbackActions.tsx index 306338459..4601cbe91 100644 --- a/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackActions/AiFeedbackActions.tsx +++ b/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackActions/AiFeedbackActions.tsx @@ -40,35 +40,35 @@ export const AiFeedbackActions: FC = props => { const votesArr: any[] = (props.actionType === 'runItem' ? (props.feedback?.votes) : (props.comment?.votes)) || [] + const setInitialVotesForFeedback = useCallback((): void => { + const initialUp = props.feedback?.upVotes ?? votesArr.filter(v => String(v.voteType) + .toLowerCase() + .includes('up')).length + const initialDown = props.feedback?.downVotes ?? votesArr.filter(v => String(v.voteType) + .toLowerCase() + .includes('down')).length + + const myVote = votesArr.find(v => String(v.createdBy) === String(loginUserInfo?.userId)) + setUpVotes(initialUp) + setDownVotes(initialDown) + setUserVote(myVote?.voteType ?? undefined) + }, []) + + const setInitialVotesForComment = useCallback((): void => { + const initialUp = votesArr.filter(v => String(v.voteType) + .toLowerCase() + .includes('up')).length + const initialDown = votesArr.filter(v => String(v.voteType) + .toLowerCase() + .includes('down')).length + + const myVote = votesArr.find(v => String(v.createdBy) === String(loginUserInfo?.userId)) + setUpVotes(initialUp) + setDownVotes(initialDown) + setUserVote(myVote?.voteType ?? undefined) + }, []) + useEffect(() => { - const setInitialVotesForFeedback = (): void => { - const initialUp = props.feedback?.upVotes ?? votesArr.filter(v => String(v.voteType) - .toLowerCase() - .includes('up')).length - const initialDown = props.feedback?.downVotes ?? votesArr.filter(v => String(v.voteType) - .toLowerCase() - .includes('down')).length - - const myVote = votesArr.find(v => String(v.createdBy) === String(loginUserInfo?.userId)) - setUpVotes(initialUp) - setDownVotes(initialDown) - setUserVote(myVote?.voteType ?? undefined) - } - - const setInitialVotesForComment = (): void => { - const initialUp = votesArr.filter(v => String(v.voteType) - .toLowerCase() - .includes('up')).length - const initialDown = votesArr.filter(v => String(v.voteType) - .toLowerCase() - .includes('down')).length - - const myVote = votesArr.find(v => String(v.createdBy) === String(loginUserInfo?.userId)) - setUpVotes(initialUp) - setDownVotes(initialDown) - setUserVote(myVote?.voteType ?? undefined) - } - if (props.actionType === 'runItem') { setInitialVotesForFeedback() } else { From f9c98adb0a551947a040d7ca1cd44be5bfd40001 Mon Sep 17 00:00:00 2001 From: Hentry Martin Date: Fri, 21 Nov 2025 00:29:38 +0100 Subject: [PATCH 6/6] fix: review comments --- .../ScorecardQuestion/AiFeedbackActions/AiFeedbackActions.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackActions/AiFeedbackActions.tsx b/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackActions/AiFeedbackActions.tsx index 4601cbe91..ab89c9263 100644 --- a/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackActions/AiFeedbackActions.tsx +++ b/src/apps/review/src/lib/components/Scorecard/ScorecardViewer/ScorecardQuestion/AiFeedbackActions/AiFeedbackActions.tsx @@ -52,7 +52,7 @@ export const AiFeedbackActions: FC = props => { setUpVotes(initialUp) setDownVotes(initialDown) setUserVote(myVote?.voteType ?? undefined) - }, []) + }, [votesArr, props.feedback]) const setInitialVotesForComment = useCallback((): void => { const initialUp = votesArr.filter(v => String(v.voteType) @@ -66,7 +66,7 @@ export const AiFeedbackActions: FC = props => { setUpVotes(initialUp) setDownVotes(initialDown) setUserVote(myVote?.voteType ?? undefined) - }, []) + }, [votesArr]) useEffect(() => { if (props.actionType === 'runItem') {