Skip to content

Commit aa97f02

Browse files
committed
PM-1906 - AI workflow - ai review scorecard UI
1 parent e1dc7fd commit aa97f02

36 files changed

+655
-109
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
@import '@libs/ui/styles/includes';
2+
3+
.wrap {
4+
}
5+
6+
.headerBar {
7+
display: flex;
8+
align-items: center;
9+
gap: $sp-4;
10+
11+
background: #00797A;
12+
padding: $sp-4;
13+
14+
font-family: "Nunito Sans", sans-serif;
15+
font-weight: bold;
16+
font-size: 16px;
17+
line-height: 22px;
18+
color: #FFFFFF;
19+
20+
cursor: pointer;
21+
transition: 0.15s ease-in-out;
22+
23+
&:hover {
24+
background: darken(#00797A, 1.5%);
25+
}
26+
27+
&:active {
28+
transition: none;
29+
background: darken(#00797A, 3%);
30+
}
31+
32+
&.toggled {
33+
.toggleBtn {
34+
svg {
35+
transform: rotate(180deg);
36+
}
37+
}
38+
}
39+
}
40+
41+
.index {
42+
width: 24px;
43+
}
44+
45+
.mx {
46+
margin: 0 auto;
47+
}
48+
49+
.toggleBtn {
50+
cursor: pointer;
51+
width: 24px;
52+
53+
svg {
54+
display: block;
55+
width: 16px;
56+
height: 16px;
57+
transition: 0.15s ease-in-out;
58+
}
59+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { FC, useCallback, useMemo } from 'react'
2+
import classNames from 'classnames'
3+
4+
import { IconOutline } from '~/libs/ui'
5+
6+
import { ScorecardGroup as ScorecardGroupModel } from '../../../../models'
7+
import { ScorecardSection } from '../ScorecardSection'
8+
import { ScorecardViewerContextValue, useScorecardContext } from '../ScorecardViewer.context'
9+
10+
import styles from './ScorecardGroup.module.scss'
11+
import { ScorecardScore } from '../ScorecardScore'
12+
import { calcGroupScore } from '../utils'
13+
14+
interface ScorecardGroupProps {
15+
index: number
16+
group: ScorecardGroupModel
17+
}
18+
19+
const ScorecardGroup: FC<ScorecardGroupProps> = props => {
20+
const { aiFeedbackItems }: ScorecardViewerContextValue = useScorecardContext()
21+
const allFeedbackItems = aiFeedbackItems || [];
22+
const { toggleItem, toggledItems }: ScorecardViewerContextValue = useScorecardContext();
23+
24+
const isVissible = !toggledItems[props.group.id];
25+
const toggle = useCallback(() => toggleItem(props.group.id), [props.group, toggleItem]);
26+
27+
const score = useMemo(() => (
28+
calcGroupScore(props.group, allFeedbackItems)
29+
), [props.group, allFeedbackItems])
30+
31+
return (
32+
<div className={styles.wrap}>
33+
<div className={classNames(styles.headerBar, isVissible && styles.toggled)} onClick={toggle}>
34+
<span className={styles.index}>
35+
{props.index}.
36+
</span>
37+
<span>
38+
{props.group.name}
39+
</span>
40+
<span className={styles.mx} />
41+
<span>
42+
<ScorecardScore
43+
score={score}
44+
scaleMax={1}
45+
scaleType='SCALE'
46+
weight={props.group.weight}
47+
/>
48+
</span>
49+
<span className={styles.toggleBtn}>
50+
<IconOutline.ChevronDownIcon />
51+
</span>
52+
</div>
53+
54+
{isVissible && props.group.sections.map((section, index) => (
55+
<ScorecardSection key={section.id} section={section} index={[props.index, index+1].join('.')} />
56+
))}
57+
</div>
58+
)
59+
}
60+
61+
export default ScorecardGroup
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as ScorecardGroup } from './ScorecardGroup'
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
@import '@libs/ui/styles/includes';
2+
3+
.wrap {
4+
p {
5+
margin-bottom: $sp-4;
6+
}
7+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { FC, useMemo } from 'react'
2+
3+
import { ScorecardViewerContextValue, useScorecardContext } from '../../ScorecardViewer.context'
4+
import { ScorecardQuestionRow } from '../ScorecardQuestionRow'
5+
6+
import styles from './AiFeedback.module.scss'
7+
import { IconAiReview } from '~/apps/review/src/lib/assets/icons'
8+
import { ScorecardQuestion } from '~/apps/review/src/lib/models'
9+
import { ScorecardScore } from '../../ScorecardScore'
10+
11+
interface AiFeedbackProps {
12+
question: ScorecardQuestion
13+
}
14+
15+
const AiFeedback: FC<AiFeedbackProps> = props => {
16+
const { aiFeedbackItems }: ScorecardViewerContextValue = useScorecardContext()
17+
const feedback = useMemo(() => aiFeedbackItems?.find(r => r.scorecardQuestionId === props.question.id), [props.question.id, aiFeedbackItems])
18+
19+
if (!aiFeedbackItems?.length || !feedback) {
20+
return <></>
21+
}
22+
23+
const isYesNo = props.question.type === 'YES_NO';
24+
25+
return (
26+
<ScorecardQuestionRow
27+
icon={<IconAiReview />}
28+
index="AI Feedback"
29+
className={styles.wrap}
30+
score={
31+
<ScorecardScore
32+
score={feedback.questionScore}
33+
scaleMax={props.question.scaleMax}
34+
scaleType={props.question.type}
35+
weight={props.question.weight}
36+
/>
37+
}
38+
>
39+
{isYesNo && (
40+
<p>
41+
<strong>{feedback.questionScore ? 'Yes' : 'No'}</strong>
42+
</p>
43+
)}
44+
{feedback.content}
45+
</ScorecardQuestionRow>
46+
)
47+
}
48+
49+
export default AiFeedback
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as AiFeedback } from './AiFeedback'
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
@import '@libs/ui/styles/includes';
2+
3+
.toggleBtn {
4+
cursor: pointer;
5+
display: block;
6+
width: 16px;
7+
height: 16px;
8+
color: #767676;
9+
transition: 0.15s ease-in-out;
10+
11+
&.toggled {
12+
transform: rotate(180deg);
13+
}
14+
}
15+
16+
.questionText {
17+
font-weight: bold;
18+
+ * {
19+
margin-top: $sp-2;
20+
}
21+
}
22+
23+
.guidelines {
24+
white-space: pre-wrap;
25+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { FC, useCallback } from 'react'
2+
3+
import { IconOutline } from '~/libs/ui'
4+
5+
import { ScorecardQuestion as ScorecardQuestionModel } from '../../../../models'
6+
7+
import styles from './ScorecardQuestion.module.scss'
8+
import { AiFeedback } from './AiFeedback'
9+
import { ScorecardQuestionRow } from './ScorecardQuestionRow'
10+
import { ScorecardViewerContextValue, useScorecardContext } from '../ScorecardViewer.context'
11+
import classNames from 'classnames'
12+
13+
interface ScorecardQuestionProps {
14+
index: string
15+
question: ScorecardQuestionModel
16+
}
17+
18+
const ScorecardQuestion: FC<ScorecardQuestionProps> = props => {
19+
const { toggleItem, toggledItems }: ScorecardViewerContextValue = useScorecardContext();
20+
21+
const isToggled = toggledItems[props.question.id!];
22+
const toggle = useCallback(() => toggleItem(props.question.id!), [props.question, toggleItem]);
23+
24+
return (
25+
<div className={styles.wrap}>
26+
<ScorecardQuestionRow
27+
icon={
28+
<IconOutline.ChevronDownIcon
29+
className={classNames(styles.toggleBtn, isToggled && styles.toggled)}
30+
onClick={toggle}
31+
/>
32+
}
33+
index={`Question ${props.index}`}
34+
className={styles.headerBar}
35+
score=''
36+
>
37+
<span className={styles.questionText}>
38+
{props.question.description}
39+
</span>
40+
{isToggled && (
41+
<div className={styles.guidelines}>
42+
{props.question.guidelines}
43+
</div>
44+
)}
45+
</ScorecardQuestionRow>
46+
<AiFeedback question={props.question} />
47+
</div>
48+
)
49+
}
50+
51+
export default ScorecardQuestion
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
@import '@libs/ui/styles/includes';
2+
3+
.wrap {
4+
display: grid;
5+
gap: $sp-4;
6+
padding: $sp-4;
7+
grid-template-columns: 24px 128px auto 144px 24px;
8+
9+
font-family: "Nunito Sans", sans-serif;
10+
font-weight: bold;
11+
font-size: 14px;
12+
line-height: 20px;
13+
color: #0A0A0A;
14+
}
15+
16+
.content {
17+
font-weight: normal;
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import classNames from 'classnames'
2+
import { FC, PropsWithChildren, ReactNode } from 'react'
3+
4+
import styles from './ScorecardQuestionRow.module.scss'
5+
6+
interface ScorecardQuestionRowProps extends PropsWithChildren {
7+
className?: string
8+
icon?: ReactNode
9+
index?: string
10+
score?: ReactNode
11+
}
12+
13+
const ScorecardQuestionRow: FC<ScorecardQuestionRowProps> = props => (
14+
<div className={classNames(props.className, styles.wrap)}>
15+
<span>{props.icon}</span>
16+
<span>{props.index}</span>
17+
<span className={styles.content}>{props.children}</span>
18+
<span>{props.score}</span>
19+
</div>
20+
)
21+
22+
export default ScorecardQuestionRow

0 commit comments

Comments
 (0)