Skip to content

feat(PM-1503): View scorecard implementation #1186

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/apps/review/src/lib/hooks/useFetchScorecard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import useSWR, { SWRResponse } from 'swr'

import { EnvironmentConfig } from '~/config'
import { xhrGetAsync } from '~/libs/core'

import { Scorecard } from '../models'

interface UseFetchScorecardParams {
id: string;
}
const baseUrl = `${EnvironmentConfig.API.V6}/review`

export function useFetchScorecard(
{
id,
}: UseFetchScorecardParams,
): Scorecard {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The return type of the useFetchScorecard function is declared as Scorecard, but it should be Scorecard | undefined to account for the initial undefined state of the data returned by useSWR.


const fetcher = (url: string): Promise<Scorecard> => xhrGetAsync<Scorecard>(url)

const { data }: SWRResponse<Scorecard, any> = useSWR<Scorecard>(
`${baseUrl}/scorecards/${id}`,
fetcher,
)

return data as Scorecard

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider handling the case where data might be undefined before returning it. This will prevent potential runtime errors if the data is not yet loaded.

}
7 changes: 7 additions & 0 deletions src/apps/review/src/lib/models/Scorecard.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
* Scorecard
*/

import { ScorecardGroup } from './ScorecardGroup.model'

export enum ProjectType {
DEVELOPMENT = 'DEVELOPMENT',
DATA_SCIENCE = 'DATA_SCIENCE',
Expand Down Expand Up @@ -94,4 +96,9 @@ export interface Scorecard {
category: string
status: ScorecardStatus
index?: number
version: string
challengeType: string
minScore: number
maxScore: number
scorecardGroups: ScorecardGroup[]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
.container {
display: flex;
flex-direction: row;
padding: 32px;
background-color: #F6F7F9;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a CSS variable for the background color #F6F7F9 to maintain consistency and ease future updates.

margin-top: 20px;
border-radius: 8px;
.left {
display: flex;
flex: 1;
flex-direction: column;
}

.item {
display: flex;
flex-direction: column;
margin-bottom: 20px;
.label {
font-weight: 700;
font-family: "Nunito Sans", sans-serif;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The font-family 'Nunito Sans' is used here. Ensure that this font is loaded correctly in the application to avoid fallback to default fonts.

font-size: 16px;
color: #000000;
margin-bottom: 6px;
}
.value {
font-weight: 400;
font-family: "Nunito Sans", sans-serif;
font-size: 16px;
color: #0A0A0A;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a CSS variable for the color #0A0A0A to maintain consistency and ease future updates.

text-transform: capitalize;
&.active {
color: #00797A;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a CSS variable for the color #00797A to maintain consistency and ease future updates.

}

&.inactive, &.deleted {
color: #767676;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a CSS variable for the color #767676 to maintain consistency and ease future updates.

font-family: 'Inter', sans-serif;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The font-family 'Inter' is used here. Ensure that this font is loaded correctly in the application to avoid fallback to default fonts.

font-weight: 600;
font-size: 14px;
}
}
}

.right {
display: flex;
flex: 3;
flex-direction: column;
.item {
display: flex;
flex-direction: column;
}
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a newline at the end of the file to follow best practices and ensure compatibility with various tools.

Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { FC } from 'react'
import cn from 'classnames'

import { ProjectTypeLabels, Scorecard, ScorecardStatusLabels, ScorecardTypeLabels } from '~/apps/review/src/lib/models'

import styles from './ScorecardDetails.module.scss'

interface ScorecardDetailsProps {
scorecard: Scorecard
}

const ScorecardDetails: FC<ScorecardDetailsProps> = (props: ScorecardDetailsProps) => {
const getStatusClassname = (): string => styles[ScorecardStatusLabels[props.scorecard.status]?.toLowerCase()]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider memoizing the getStatusClassname function using useCallback to prevent unnecessary re-calculations on each render.

return (
<div className={styles.container}>
<div className={styles.left}>
<div className={styles.item}>
<div className={styles.label}>Version</div>
<div className={styles.value}>{props.scorecard.version}</div>
</div>
<div className={styles.item}>
<div className={styles.label}>Type</div>
<div className={styles.value}>{ScorecardTypeLabels[props.scorecard.type]}</div>
</div>
<div className={styles.item}>
<div className={styles.label}>Project Type</div>
<div className={styles.value}>{ProjectTypeLabels[props.scorecard.challengeTrack]}</div>
</div>
</div>
<div className={styles.right}>
<div className={styles.item}>
<div className={styles.label}>Category</div>
<div className={styles.value}>{props.scorecard.challengeType}</div>
</div>
<div className={styles.item}>
<div className={styles.label}>Status</div>
<div
className={cn(styles.value, getStatusClassname())}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cn function is used for conditional class names. Ensure that getStatusClassname() always returns a valid class name to avoid potential runtime errors.

>
{ScorecardStatusLabels[props.scorecard.status]}
</div>
</div>
<div className={styles.item}>
<div className={styles.label}>Min - Max. Score</div>
<div className={styles.value}>{`${props.scorecard.minScore} - ${props.scorecard.maxScore}`}</div>
</div>
</div>
</div>
)
}

export default ScorecardDetails
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as ScorecardDetails } from './ScorecardDetails'
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
@import '@libs/ui/styles/includes';

.container {
margin-top: 32px;
.group {
.heading {
padding: 12px 16px;
background-color: #0F172A;
color: #ffffff;
display: flex;
flex-direction: column;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
.groupNumber {
font-size: 14px;
font-family: "Nunito Sans", sans-serif;
font-weight: 400;
}
.groupInfo {
display: flex;
flex-direction: row;
font-size: 16px;
font-family: "Nunito Sans", sans-serif;
margin-top: 4px;
.name {
display: flex;
flex: 1;
font-weight: 700;
}
}
}
}
}

@media (max-width: #{$lg-max}) {
.container {
.group {
.heading {
.groupInfo {
flex-direction: column;
.name {
margin-bottom: 8px;
}
}
}
}
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good practice to ensure that files end with a newline character. This can help prevent issues with certain tools and version control systems.

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { FC } from 'react'

import { ScorecardGroup } from '~/apps/review/src/lib/models'

import ScorecardSections from '../ScorecardSections/ScorecardSections'

import styles from './ScorecardGroups.module.scss'

interface ScorecardGroupsProps {
groups: ScorecardGroup[]
}

const ScorecardGroups: FC<ScorecardGroupsProps> = (props: ScorecardGroupsProps) => (
<div className={styles.container}>
{
props.groups.map((group, index) => (
<div key={group.id} className={styles.group}>
<div className={styles.heading}>
<div className={styles.groupNumber}>{`Group ${index + 1}`}</div>
<div className={styles.groupInfo}>
<div className={styles.name}>{group.name}</div>
<div>{group.weight}</div>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a label or unit to the group.weight value for better clarity, especially if it represents a specific metric or percentage.

</div>
</div>
<ScorecardSections sections={group.sections} />
</div>
))
}
</div>
)

export default ScorecardGroups
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as ScorecardGroups } from './ScorecardGroups'
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
@import '@libs/ui/styles/includes';

.container {
background-color: #E0E4E84D;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a more descriptive variable name for the color value #E0E4E84D to improve readability and maintainability.

padding: 40px 32px;
.section {
.heading {
padding: 12px 16px;
background-color: #00797A;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a more descriptive variable name for the color value #00797A to improve readability and maintainability.

color: #ffffff;
display: flex;
flex-direction: column;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
.sectionInfo {
display: flex;
flex-direction: row;
margin-top: 4px;
font-size: 16px;
font-family: "Nunito Sans", sans-serif;
.name {
display: flex;
flex: 1;
font-weight: 700;
}
}
}
.questions {
background-color: #FFFFFF;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a more descriptive variable name for the color value #FFFFFF to improve readability and maintainability.

padding: 32px 20px;
.question {
background-color: #F6F7F9;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a more descriptive variable name for the color value #F6F7F9 to improve readability and maintainability.

padding: 20px 16px;
display: flex;
flex-direction: row;
color: #0A0A0A;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a more descriptive variable name for the color value #0A0A0A to improve readability and maintainability.

margin-bottom: 0px;
&.notLast {
margin-bottom: 20px;
}
.left {
display: flex;
flex: 4;
flex-direction: column;
.description {
font-weight: 700;
font-size: 16px;
}
.detailItemsWrapper {
margin-top: 16px;
.detailItem {
display: flex;
flex-direction: row;
font-family: "Nunito Sans", sans-serif;
margin-bottom: 8px;
.label {
font-weight: 700;
font-size: 14px;
min-width: 142px;
}
.value {
font-weight: 400;
font-size: 14px;
}
}
}
}
.right {
display: flex;
flex: 1;
justify-content: flex-end;
}
}
}
}
}

@media (max-width: #{$lg-max}) {
.container {
.section {
.heading {
.sectionInfo {
flex-direction: column;
.name {
margin-bottom: 4px;
}
}
}
.questions {
.question {
flex-direction: column;
.left {
.detailItemsWrapper {
.detailItem {
flex-direction: column;
min-width: auto;
}
}
}
.right {
justify-content: flex-start;
}
}
}
}
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a newline at the end of the file to adhere to POSIX standards and improve compatibility with various tools.

Loading