Skip to content

Commit 136bb49

Browse files
authored
Merge pull request #1196 from topcoder-platform/PM-1504_edit-create-scorecard
PM-1504 edit create scorecard
2 parents ec33240 + 9d0d3da commit 136bb49

File tree

28 files changed

+1343
-49
lines changed

28 files changed

+1343
-49
lines changed

src/apps/review/src/lib/components/TableScorecards/TableScorecards.tsx

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ export const TableScorecards: FC<Props> = (props: Props) => {
4848
className: classNames(styles.textBlue, styles.tableBreakCell, styles.tableCell),
4949
label: 'Scorecard',
5050
propertyName: 'name',
51-
renderer: (data: Scorecard) => (
52-
<Link to={`${data.id}`}>
53-
{data.name}
51+
renderer: (scorecard: Scorecard) => (
52+
<Link to={`${scorecard.id}`}>
53+
{scorecard.name}
5454
</Link>
5555
),
5656
type: 'element',
@@ -59,17 +59,17 @@ export const TableScorecards: FC<Props> = (props: Props) => {
5959
className: classNames(styles.tableCell),
6060
label: 'Type',
6161
propertyName: 'type',
62-
renderer: (data: Scorecard) => (
63-
<div>{ScorecardTypeLabels[data.type]}</div>
62+
renderer: (scorecard: Scorecard) => (
63+
<div>{ScorecardTypeLabels[scorecard.type]}</div>
6464
),
6565
type: 'element',
6666
},
6767
{
6868
className: styles.tableCell,
6969
label: 'Project Type',
7070
propertyName: 'challengeTrack',
71-
renderer: (data: Scorecard) => (
72-
<div>{ProjectTypeLabels[data.challengeTrack as ProjectType]}</div>
71+
renderer: (scorecard: Scorecard) => (
72+
<div>{ProjectTypeLabels[scorecard.challengeTrack as ProjectType]}</div>
7373
),
7474
type: 'element',
7575
},
@@ -83,21 +83,21 @@ export const TableScorecards: FC<Props> = (props: Props) => {
8383
className: styles.tableCell,
8484
label: 'Status',
8585
propertyName: 'status',
86-
renderer: (data: Scorecard) => (
87-
<div>{ScorecardStatusLabels[data.status]}</div>
86+
renderer: (scorecard: Scorecard) => (
87+
<div>{ScorecardStatusLabels[scorecard.status]}</div>
8888
),
8989
type: 'element',
9090
},
9191
{
9292
className: classNames(styles.tableCell, styles.tableCellCenter),
9393
label: 'Action',
94-
renderer: (data: Scorecard) => (
94+
renderer: (scorecard: Scorecard) => (
9595
<div className={styles.action}>
96-
<div className={styles.actionItem}>
96+
<Link className={styles.actionItem} to={`${scorecard.id}/edit`}>
9797
<PencilIcon />
9898
<span>Edit</span>
99-
</div>
100-
<div className={styles.actionItem} onClick={bind(props.onClone, this, data)}>
99+
</Link>
100+
<div className={styles.actionItem} onClick={bind(props.onClone, this, scorecard)}>
101101
<DuplicateIcon />
102102
<span>Clone</span>
103103
</div>

src/apps/review/src/lib/hooks/useFetchScorecard.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,27 @@ import { xhrGetAsync } from '~/libs/core'
55

66
import { Scorecard } from '../models'
77

8-
interface UseFetchScorecardParams {
9-
id: string;
10-
}
118
const baseUrl = `${EnvironmentConfig.API.V6}/review`
129

13-
export function useFetchScorecard(
14-
{
15-
id,
16-
}: UseFetchScorecardParams,
17-
): Scorecard {
10+
interface ScorecardResponse {
11+
scorecard: Scorecard | undefined
12+
error?: any
13+
isValidating: boolean
14+
}
15+
16+
export function useFetchScorecard(id: string | undefined): ScorecardResponse {
1817

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

21-
const { data }: SWRResponse<Scorecard, any> = useSWR<Scorecard>(
22-
`${baseUrl}/scorecards/${id}`,
20+
const { data, error, isValidating }: SWRResponse<Scorecard, any> = useSWR<Scorecard>(
21+
// eslint-disable-next-line unicorn/no-null
22+
id ? `${baseUrl}/scorecards/${id}` : null,
2323
fetcher,
2424
)
2525

26-
return data as Scorecard
26+
return {
27+
error,
28+
isValidating,
29+
scorecard: data,
30+
}
2731
}

src/apps/review/src/lib/models/Scorecard.model.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export const categoryByProjectType = {
3737
'Architecture',
3838
'Deployment',
3939
'Process',
40+
'Assembly Competition',
4041
'UI Prototype Competition',
4142
'Conceptualization',
4243
'RIA Build Competition',
@@ -54,6 +55,9 @@ export const categoryByProjectType = {
5455
],
5556
} satisfies Record<ProjectType, string[]>
5657

58+
export const scorecardCategories = Object.values(categoryByProjectType)
59+
.flat()
60+
5761
export enum ScorecardStatus {
5862
ACTIVE = 'ACTIVE',
5963
INACTIVE = 'INACTIVE',
@@ -88,17 +92,25 @@ export const ScorecardTypeLabels: Record<ScorecardType, string> = {
8892
[ScorecardType.ITERATIVE_REVIEW]: 'Iterative Review',
8993
}
9094

95+
export const ScorecardScales = {
96+
'scale(1-4)': 'Scale 1-4',
97+
'scale(1-5)': 'Scale 1-5',
98+
'scale(1-10)': 'Scale 1-10',
99+
'scale(1-100)': 'Scale 1-100',
100+
test_case: 'Test Case',
101+
yes_no: 'Yes / No',
102+
}
103+
91104
export interface Scorecard {
92-
id: string
105+
id?: string
93106
name: string
94107
type: ScorecardType
95108
challengeTrack: ProjectType
96-
category: string
97109
status: ScorecardStatus
98110
index?: number
99-
version: string
100-
challengeType: string
101111
minScore: number
102112
maxScore: number
113+
challengeType: string
114+
version: string
103115
scorecardGroups: ScorecardGroup[]
104116
}

src/apps/review/src/lib/services/scorecards.service.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* Scorecards service
33
*/
4-
import { xhrPostAsync } from '~/libs/core'
4+
import { xhrPostAsync, xhrPutAsync } from '~/libs/core'
55
import { EnvironmentConfig } from '~/config'
66

77
import { MockScorecard } from '../../mock-datas'
@@ -24,3 +24,16 @@ export const fetchScorecards
2424
export const cloneScorecard = async (scorecard: Pick<Scorecard, 'id'>): Promise<Scorecard> => (
2525
xhrPostAsync(`${baseUrl}/${scorecard.id}/clone`, {})
2626
)
27+
28+
/**
29+
* Save scorecard
30+
* @param scorecard Scorecard data to save
31+
* @returns resolves to the saved scorecard data
32+
*/
33+
export const saveScorecard = async (scorecard: Scorecard): Promise<Scorecard> => {
34+
if (!scorecard.id) {
35+
return xhrPostAsync<Scorecard, Scorecard>(`${baseUrl}`, scorecard)
36+
}
37+
38+
return xhrPutAsync<Scorecard, Scorecard>(`${baseUrl}/${scorecard.id}`, scorecard)
39+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { omit } from 'lodash'
2+
import { createContext, ReactNode, useContext, useMemo } from 'react'
3+
4+
import { ConfirmationProps, useConfirmationModal } from '~/libs/ui'
5+
6+
export interface EditScorecardPageContextProps {
7+
children: ReactNode;
8+
}
9+
10+
export type EditScorecardPageContextValue = {
11+
confirm: (prosp: ConfirmationProps) => Promise<boolean>,
12+
};
13+
14+
const EditScorecardPageContext = createContext({} as EditScorecardPageContextValue)
15+
16+
export const EditScorecardPageContextProvider = (props: EditScorecardPageContextProps): JSX.Element => {
17+
const confirmation = useConfirmationModal()
18+
19+
const ctxVal = useMemo(() => ({
20+
confirm: confirmation.confirm,
21+
}), [confirmation])
22+
23+
return (
24+
<EditScorecardPageContext.Provider
25+
value={ctxVal}
26+
{...omit(props, 'children')}
27+
>
28+
{props.children}
29+
{confirmation.modal}
30+
</EditScorecardPageContext.Provider>
31+
)
32+
}
33+
34+
export const usePageContext = (): EditScorecardPageContextValue => useContext(EditScorecardPageContext)
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
@import '@libs/ui/styles/includes';
2+
3+
.pageContentWrap {
4+
color: #0A0A0A;
5+
font-family: Inter;
6+
7+
hr {
8+
margin: $sp-6 0;
9+
}
10+
}
11+
12+
.pageTitle {
13+
font-size: 32px;
14+
line-height: 36px;
15+
font-weight: bold;
16+
color: #0A0A0A;
17+
margin-bottom: $sp-8;
18+
text-transform: none;
19+
}
20+
21+
.sectionTitle {
22+
font-size: 24px;
23+
line-height: 32px;
24+
font-weight: bold;
25+
margin-bottom: $sp-5;
26+
margin-top: $sp-11;
27+
text-transform: none;
28+
&:first-child {
29+
margin-top: $sp-8;
30+
}
31+
32+
}
33+
34+
.grayWrapper {
35+
background-color: #F6F7F9;
36+
border-radius: $sp-2;
37+
padding: $sp-8;
38+
}
39+
40+
.scorecardInfo {
41+
display: flex;
42+
flex-direction: row;
43+
flex-wrap: wrap;
44+
gap: $sp-3 $sp-16;
45+
}
46+
47+
.headerArea {
48+
border-radius: $sp-2 $sp-2 0 0;
49+
padding: $sp-3 $sp-4 $sp-6;
50+
51+
display: flex;
52+
flex-wrap: wrap;
53+
gap: $sp-2 $sp-4;
54+
55+
&Label {
56+
width: 100%;
57+
}
58+
59+
&Inputs {
60+
display: flex;
61+
gap: $sp-4;
62+
width: 100%;
63+
}
64+
}
65+
66+
.contentArea {
67+
padding: $sp-5 $sp-4;
68+
}
69+
70+
.groupWrap {
71+
.headerArea {
72+
background: #0F172A;
73+
color: $tc-white;
74+
}
75+
76+
.contentArea {
77+
background: #E0E4E84D;
78+
padding: $sp-10 $sp-8;
79+
}
80+
}
81+
82+
.sectionWrap {
83+
.headerArea {
84+
background: $teal-160;
85+
color: $tc-white;
86+
}
87+
88+
.contentArea {
89+
background: $tc-white;
90+
}
91+
}
92+
93+
.questionWrap {
94+
.questionItem {
95+
width: 100%;
96+
padding: $sp-5 $sp-4;
97+
background: #F6F7F9;
98+
99+
display: grid;
100+
grid-template-columns: 1fr 7.85% 3.5%;
101+
gap: $sp-4;
102+
103+
:global(.main-group) {
104+
grid-column: 1;
105+
}
106+
107+
:global(.weight-group) {
108+
grid-column: 2;
109+
}
110+
111+
:global(.action-group) {
112+
grid-column: 3;
113+
}
114+
115+
+ .questionItem {
116+
margin-top: $sp-4;
117+
}
118+
}
119+
120+
.headerAreaLabel {
121+
width: 100%;
122+
}
123+
}
124+
125+
.xlWidthInput {
126+
flex: 0 1 86%;
127+
}
128+
129+
.smWidthInput {
130+
flex: 0 0 7.85%;
131+
min-width: 88px;
132+
}
133+
134+
.mdWidthInput {
135+
flex: 0 0 40%;
136+
}
137+
138+
.doubleInputWrap {
139+
display: flex;
140+
gap: $sp-3;
141+
142+
> * {
143+
flex: 1;
144+
}
145+
}
146+
147+
.trashIcon {
148+
flex: 0 0 3.5%;
149+
width: 24px;
150+
height: 38px;
151+
padding: 2px;
152+
&.blue {
153+
color: #0D61BF;
154+
}
155+
}
156+
157+
.footerArea {
158+
display: flex;
159+
gap: $sp-4;
160+
justify-content: space-between;
161+
align-items: center;
162+
margin-top: $sp-5;
163+
}
164+
165+
.bottomContainer {
166+
margin-top: $sp-8;
167+
}
168+
169+
.buttonsWrap {
170+
display: flex;
171+
gap: $sp-2;
172+
justify-content: flex-end;
173+
align-items: center;
174+
}

0 commit comments

Comments
 (0)