Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit e065422

Browse files
authoredJul 15, 2021
Merge pull request #378 from topcoder-platform/roles-milestone18
Roles milestone18 - High Complexity issues
2 parents c7b3739 + 975a7fc commit e065422

File tree

12 files changed

+438
-22
lines changed

12 files changed

+438
-22
lines changed
 

‎src/constants/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ export const ACTION_TYPE = {
264264
*/
265265
ADD_MATCHING_ROLE: "ADD_MATCHING_ROLE",
266266
DELETE_MATCHING_ROLE: "DELETE_MATCHING_ROLE",
267+
EDIT_MATCHING_ROLE: "EDIT_MATCHING_ROLE",
267268
};
268269

269270
/**

‎src/routes/CreateNewTeam/actions/index.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ const deleteMatchingRole = () => ({
3636
type: ACTION_TYPE.DELETE_MATCHING_ROLE,
3737
});
3838

39+
const editMatchingRole = (role) => ({
40+
type: ACTION_TYPE.EDIT_MATCHING_ROLE,
41+
payload: role,
42+
});
43+
3944
export const clearSearchedRoles = () => (dispatch, getState) => {
4045
dispatch(clearRoles());
4146
updateLocalStorage(getState().searchedRoles);
@@ -51,6 +56,11 @@ export const addRoleSearchId = (id) => (dispatch, getState) => {
5156
updateLocalStorage(getState().searchedRoles);
5257
};
5358

59+
export const editRoleAction = (role) => (dispatch, getState) => {
60+
dispatch(editMatchingRole(role));
61+
updateLocalStorage(getState().searchedRoles);
62+
};
63+
5464
export const deleteSearchedRole = (id) => (dispatch, getState) => {
5565
dispatch(deleteRole(id));
5666
updateLocalStorage(getState().searchedRoles);
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/**
2+
* Edit Role Modal
3+
* Popup form to enter details about current role
4+
*/
5+
import React, { useEffect, useState } from "react";
6+
import PT from "prop-types";
7+
import { Form, Field, useField } from "react-final-form";
8+
import FormField from "components/FormField";
9+
import BaseCreateModal from "../BaseCreateModal";
10+
import Button from "components/Button";
11+
import MonthPicker from "components/MonthPicker";
12+
import InformationTooltip from "components/InformationTooltip";
13+
import IconCrossLight from "../../../../assets/images/icon-cross-light.svg";
14+
import "./styles.module.scss";
15+
import NumberInput from "components/NumberInput";
16+
import { validator, validateExists, validateMin, composeValidators } from "./utils/validator";
17+
18+
const Error = ({ name }) => {
19+
const {
20+
meta: { dirty, error },
21+
} = useField(name, { subscription: { dirty: true, error: true } });
22+
return dirty && error ? <span styleName="error">{error}</span> : null;
23+
};
24+
25+
function EditRoleModal({ open, onClose, submitForm, role }) {
26+
const [startMonthVisible, setStartMonthVisible] = useState(false);
27+
28+
return (
29+
<Form
30+
onSubmit={submitForm}
31+
mutators={{
32+
clearField: ([fieldName], state, { changeValue }) => {
33+
changeValue(state, fieldName, () => undefined);
34+
},
35+
}}
36+
validate={validator}
37+
>
38+
{({
39+
handleSubmit,
40+
hasValidationErrors,
41+
form: {
42+
mutators: { clearField },
43+
getState,
44+
},
45+
}) => {
46+
return (
47+
<BaseCreateModal
48+
open={open}
49+
onClose={onClose}
50+
title="Edit Role"
51+
subtitle="Please provide your team details before submitting a request."
52+
buttons={
53+
<Button
54+
type="primary"
55+
size="medium"
56+
onClick={handleSubmit}
57+
disabled={hasValidationErrors}
58+
>
59+
Submit
60+
</Button>
61+
}
62+
disableFocusTrap
63+
>
64+
<div styleName="modal-body">
65+
<table styleName="table">
66+
<tr>
67+
<th># of resources</th>
68+
<th>Duration (weeks)</th>
69+
<th>Start month</th>
70+
</tr>
71+
<tr styleName="role-row">
72+
<td>
73+
<Field
74+
validate={composeValidators(validateExists, validateMin(1))}
75+
name="numberOfResources"
76+
initialValue={role.numberOfResources}
77+
>
78+
{({ input, meta }) => (
79+
<NumberInput
80+
name={input.name}
81+
value={input.value}
82+
onChange={input.onChange}
83+
onBlur={input.onBlur}
84+
onFocus={input.onFocus}
85+
min="1"
86+
error={meta.touched && meta.error}
87+
/>
88+
)}
89+
</Field>
90+
<Error name="numberOfResources" />
91+
</td>
92+
<td>
93+
<Field
94+
validate={composeValidators(validateExists, validateMin(4))}
95+
name="durationWeeks"
96+
initialValue={role.durationWeeks}
97+
>
98+
{({ input, meta }) => (
99+
<NumberInput
100+
name={input.name}
101+
value={input.value}
102+
onChange={input.onChange}
103+
onBlur={input.onBlur}
104+
onFocus={input.onFocus}
105+
min="4"
106+
error={meta.touched && meta.error}
107+
/>
108+
)}
109+
</Field>
110+
<Error name="durationWeeks" />
111+
</td>
112+
<td>
113+
{startMonthVisible ? (
114+
<>
115+
<Field
116+
name="startMonth"
117+
initialValue={Date.now()}
118+
>
119+
{(props) => (
120+
<MonthPicker
121+
name={props.input.name}
122+
value={props.input.value}
123+
onChange={props.input.onChange}
124+
onBlur={props.input.onBlur}
125+
onFocus={props.input.onFocus}
126+
/>
127+
)}
128+
</Field>
129+
<Error name="startMonth" />
130+
</>
131+
) : (
132+
<div styleName="flex-container">
133+
<button
134+
styleName="toggle-button"
135+
onClick={() =>
136+
setStartMonthVisible(true)
137+
}
138+
>
139+
Add Start Month
140+
</button>
141+
<InformationTooltip
142+
iconSize="14px"
143+
text="Requested start month for this position."
144+
/>
145+
</div>
146+
)}
147+
</td>
148+
</tr>
149+
</table>
150+
</div>
151+
</BaseCreateModal>
152+
);
153+
}}
154+
</Form>
155+
);
156+
}
157+
158+
EditRoleModal.propTypes = {
159+
open: PT.bool,
160+
onClose: PT.func,
161+
submitForm: PT.func,
162+
role: PT.object,
163+
};
164+
165+
export default EditRoleModal;
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
@import "styles/include";
2+
3+
.toggle-button {
4+
@include font-roboto;
5+
outline: none;
6+
border: none;
7+
background: none;
8+
font-size: 12px;
9+
font-weight: 500;
10+
color: #137D60;
11+
padding: 1px 6px 0 6px;
12+
13+
&.toggle-description {
14+
margin-top: 12px;
15+
> span {
16+
font-size: 18px;
17+
vertical-align: middle;
18+
}
19+
}
20+
}
21+
22+
.table {
23+
margin-top: 40px;
24+
width: 100%;
25+
th {
26+
@include font-roboto;
27+
font-size: 12px;
28+
color: #555;
29+
padding-bottom: 7px;
30+
border-bottom: 1px solid #d4d4d4;
31+
32+
&.bold {
33+
font-weight: 700;
34+
}
35+
}
36+
37+
.role-row {
38+
td {
39+
padding: 18px 18px 18px 0;
40+
vertical-align: top;
41+
@include font-barlow;
42+
font-weight: 600;
43+
font-size: 16px;
44+
color: #2a2a2a;
45+
border-bottom: 1px solid #e9e9e9;
46+
47+
&:last-child {
48+
padding-right: 0;
49+
}
50+
51+
input {
52+
@include font-roboto;
53+
font-size: 14px;
54+
line-height: normal;
55+
height: 34px;
56+
&[type="number"] {
57+
width: 98px;
58+
}
59+
}
60+
}
61+
}
62+
}
63+
64+
.flex-container {
65+
display: flex;
66+
flex-direction: row;
67+
align-items: center;
68+
justify-content: flex-start;
69+
width: 118px;
70+
margin-top: 13px;
71+
}
72+
73+
.error {
74+
font-size: 14px;
75+
font-weight: 400;
76+
color: red;
77+
display: block;
78+
}
79+
80+
.delete-role {
81+
border: none;
82+
background: none;
83+
84+
margin-top: 13px;
85+
86+
&:hover {
87+
g {
88+
stroke: red;
89+
}
90+
}
91+
}
92+
93+
.modal-body {
94+
overflow-x: auto;
95+
textarea {
96+
height: 95px;
97+
}
98+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
const composeValidators = (...validators) => (value) =>
2+
validators.reduce((error, validator) => error || validator(value), undefined);
3+
4+
const validateMin = (min) => (value) =>
5+
isNaN(value) || value >= min ? undefined : `Should be greater than ${min}`;
6+
7+
const validateName = (name) => {
8+
if (!name || name.trim().length === 0) {
9+
return "Please enter a team name.";
10+
}
11+
return undefined;
12+
};
13+
14+
const validateNumber = (number) => {
15+
const converted = Number(number);
16+
17+
if (
18+
Number.isNaN(converted) ||
19+
converted !== Math.floor(converted) ||
20+
converted < 1
21+
) {
22+
return "Please enter a positive integer";
23+
}
24+
return undefined;
25+
};
26+
27+
const validateMonth = (monthString) => {
28+
const then = new Date(monthString);
29+
const now = new Date();
30+
const thenYear = then.getFullYear();
31+
const nowYear = now.getFullYear();
32+
const thenMonth = then.getMonth();
33+
const nowMonth = now.getMonth();
34+
35+
if (thenYear < nowYear || (thenYear === nowYear && thenMonth < nowMonth)) {
36+
return "Start month may not be before current month";
37+
}
38+
return undefined;
39+
};
40+
41+
const validator = (role) => {
42+
const roleErrors = {};
43+
roleErrors.numberOfResources = validateNumber(role.numberOfResources);
44+
roleErrors.durationWeeks = validateNumber(role.durationWeeks);
45+
if (role.startMonth) {
46+
roleErrors.startMonth = validateMonth(role.startMonth);
47+
}
48+
49+
return roleErrors;
50+
};
51+
52+
const validateExists = (value) => {
53+
return value === undefined ? "Please enter a positive integer" : undefined;
54+
};
55+
56+
export { validator, validateExists, validateMin, composeValidators };

‎src/routes/CreateNewTeam/components/ResultCard/index.jsx

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,10 @@ function ResultCard({ role }) {
7878
</Button>
7979
</div>
8080
{showRates && !isExternalMember && (
81-
<div styleName="xeno-rates">
81+
<div styleName="wipro-rates">
8282
{userHandle && (
8383
<p styleName="greeting-txt">
84-
Hi {userHandle}, we have special rates for you as a Xeno User!
84+
Hi {userHandle}, we have special rates for you as a Wipro User!
8585
</p>
8686
)}
8787
<div styleName="rates">
@@ -97,8 +97,15 @@ function ResultCard({ role }) {
9797
<p>/Week</p>
9898
</div>
9999
</div>
100-
<div styleName="in-country">
101-
<h4>In-Country Rate</h4>
100+
<div styleName="global-niche">
101+
<h4>Global Niche Rate</h4>
102+
<div styleName="cost">
103+
<h4>{formatRate(rates.niche)}</h4>
104+
<p>/Week</p>
105+
</div>
106+
</div>
107+
<div styleName="offshore-niche">
108+
<h4>Offshore Niche Rate</h4>
102109
<div styleName="cost">
103110
<h4>{formatRate(rates.inCountry)}</h4>
104111
<p>/Week</p>
@@ -124,8 +131,15 @@ function ResultCard({ role }) {
124131
<p>/Week</p>
125132
</div>
126133
</div>
127-
<div styleName="in-country">
128-
<h4>In-Country Rate</h4>
134+
<div styleName="global-niche">
135+
<h4>Global Niche Rate</h4>
136+
<div styleName="cost">
137+
<h4>{formatRate(rates.rate30Niche)}</h4>
138+
<p>/Week</p>
139+
</div>
140+
</div>
141+
<div styleName="offshore-niche">
142+
<h4>Offshore Niche Rate</h4>
129143
<div styleName="cost">
130144
<h4>{formatRate(rates.rate30InCountry)}</h4>
131145
<p>/Week</p>
@@ -151,8 +165,16 @@ function ResultCard({ role }) {
151165
<p>/Week</p>
152166
</div>
153167
</div>
154-
<div styleName="in-country">
155-
<h4>In-Country Rate</h4>
168+
169+
<div styleName="global-niche">
170+
<h4>Global Niche Rate</h4>
171+
<div styleName="cost">
172+
<h4>{formatRate(rates.rate20Niche)}</h4>
173+
<p>/Week</p>
174+
</div>
175+
</div>
176+
<div styleName="offshore-niche">
177+
<h4>Offshore Niche Rate</h4>
156178
<div styleName="cost">
157179
<h4>{formatRate(rates.rate20InCountry)}</h4>
158180
<p>/Week</p>

‎src/routes/CreateNewTeam/components/ResultCard/styles.module.scss

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@
138138
padding-bottom: 50px;
139139
}
140140

141-
.xeno-rates {
141+
.wipro-rates {
142142
display: flex;
143143
flex-direction: column;
144144
padding: 0 25px 50px 52px;
@@ -180,7 +180,8 @@
180180
}
181181
}
182182
.global,
183-
.in-country,
183+
.global-niche,
184+
.offshore-niche,
184185
.offshore {
185186
display: flex;
186187
flex-direction: column;
@@ -225,7 +226,12 @@
225226
.global::before {
226227
background-color: #c99014;
227228
}
228-
.in-country::before {
229+
230+
.global-niche::before {
231+
background-color: #0ab88a;
232+
}
233+
234+
.offshore-niche::before {
229235
background-color: #716d67;
230236
}
231237
.offshore::before {

‎src/routes/CreateNewTeam/components/SearchAndSubmit/index.jsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@ import InputContainer from "../InputContainer";
1414
import SearchContainer from "../SearchContainer";
1515
import SubmitContainer from "../SubmitContainer";
1616

17+
const SEARCHINGTIME = 1600;
18+
1719
function SearchAndSubmit(props) {
1820
const { stages, setStages, searchObject, onClick, page } = props;
1921

2022
const [searchState, setSearchState] = useState(null);
23+
const [isNewRole, setIsNewRole] = useState(false);
2124

2225
const { matchingRole } = useSelector((state) => state.searchedRoles);
2326

@@ -48,12 +51,14 @@ function SearchAndSubmit(props) {
4851
if (previousSearchId) {
4952
searchObjectCopy.previousRoleSearchRequestId = previousSearchId;
5053
}
54+
const searchingBegin = Date.now();
5155
searchRoles(searchObjectCopy)
5256
.then((res) => {
5357
const name = _.get(res, "data.name");
5458
const searchId = _.get(res, "data.roleSearchRequestId");
5559
if (name && !isCustomRole({ name })) {
56-
dispatch(addSearchedRole({ searchId, name }));
60+
dispatch(addSearchedRole({ searchId, name, numberOfResources: 1, durationWeeks: 4 }));
61+
setIsNewRole(true)
5762
} else if (searchId) {
5863
dispatch(addRoleSearchId(searchId));
5964
}
@@ -63,8 +68,13 @@ function SearchAndSubmit(props) {
6368
console.error(err);
6469
})
6570
.finally(() => {
66-
setCurrentStage(2, stages, setStages);
67-
setSearchState("done");
71+
_.delay(
72+
() => {
73+
setCurrentStage(2, stages, setStages);
74+
setSearchState("done");
75+
},
76+
Date.now() - searchingBegin > SEARCHINGTIME ? 0 : 1500
77+
);
6878
});
6979
// eslint-disable-next-line react-hooks/exhaustive-deps
7080
}, [dispatch, previousSearchId, searchObject]);
@@ -80,9 +90,11 @@ function SearchAndSubmit(props) {
8090
/>
8191
<SearchContainer
8292
path="search"
93+
previousSearchId={previousSearchId}
8394
addedRoles={addedRoles}
8495
searchState={searchState}
8596
matchingRole={matchingRole}
97+
isNewRole={isNewRole}
8698
{...props}
8799
/>
88100
<SubmitContainer

‎src/routes/CreateNewTeam/components/SearchCard/index.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ function SearchCard() {
1919
setSearchState("state1");
2020
timer2 = setTimeout(() => {
2121
setSearchState("state2");
22-
}, 800);
23-
}, 800);
22+
}, 500);
23+
}, 500);
2424

2525
return () => {
2626
clearTimeout(timer1);

‎src/routes/CreateNewTeam/components/SearchContainer/index.jsx

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,48 @@
55
* search pages. Contains logic and supporting
66
* components for searching for roles.
77
*/
8-
import React, { useCallback, useState } from "react";
8+
import React, { useCallback, useState, useMemo, useEffect } from "react";
99
import PT from "prop-types";
10+
import { useDispatch } from "react-redux";
11+
import { editRoleAction } from "../../actions";
1012
import AddedRolesAccordion from "../AddedRolesAccordion";
1113
import Completeness from "../Completeness";
1214
import SearchCard from "../SearchCard";
1315
import ResultCard from "../ResultCard";
16+
import EditRoleModal from '../EditRoleModal'
1417
import NoMatchingProfilesResultCard from "../NoMatchingProfilesResultCard";
1518
import { isCustomRole } from "utils/helpers";
1619
import AddAnotherModal from "../AddAnotherModal";
1720
import "./styles.module.scss";
1821

1922
function SearchContainer({
23+
isNewRole,
2024
stages,
2125
completenessStyle,
2226
navigate,
2327
addedRoles,
2428
searchState,
29+
previousSearchId,
2530
matchingRole,
2631
}) {
2732
const [addAnotherOpen, setAddAnotherOpen] = useState(false);
33+
const [showEditModal, setShowEditModal] = useState(false);
34+
35+
const dispatch = useDispatch();
36+
const currentRole = useMemo(() => {
37+
return _.find(addedRoles, { searchId: previousSearchId });
38+
}, [addedRoles, previousSearchId]);
39+
40+
useEffect(() => {
41+
if (isNewRole) {
42+
setShowEditModal(true)
43+
}
44+
}, [isNewRole]);
45+
46+
const onSubmitEditRole = useCallback((role) => {
47+
setShowEditModal(false)
48+
dispatch(editRoleAction({...role, searchId: previousSearchId}))
49+
}, [addedRoles, previousSearchId]);
2850

2951
const onSubmit = useCallback(() => {
3052
setAddAnotherOpen(false);
@@ -64,6 +86,12 @@ function SearchContainer({
6486
percentage={getPercentage()}
6587
/>
6688
</div>
89+
{showEditModal && <EditRoleModal
90+
role={currentRole}
91+
open={showEditModal}
92+
onClose={() => setShowEditModal(false)}
93+
submitForm={onSubmitEditRole}
94+
/>}
6795
<AddAnotherModal
6896
open={addAnotherOpen}
6997
onClose={() => setAddAnotherOpen(false)}
@@ -76,8 +104,10 @@ function SearchContainer({
76104
}
77105

78106
SearchContainer.propTypes = {
107+
isNewRole: PT.bool,
79108
stages: PT.array,
80109
completenessStyle: PT.string,
110+
previousSearchId: PT.string,
81111
navigate: PT.func,
82112
addedRoles: PT.array,
83113
searchState: PT.string,

‎src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,14 +133,14 @@ function TeamDetailsModal({ open, onClose, submitForm, addedRoles }) {
133133
<th>Start month</th>
134134
<th></th>
135135
</tr>
136-
{addedRoles.map(({ searchId: id, name }) => (
136+
{addedRoles.map(({ searchId: id, name, numberOfResources, durationWeeks, startMonth }) => (
137137
<tr styleName="role-row" key={id}>
138138
<td>{name}</td>
139139
<td>
140140
<Field
141141
validate={validateExists}
142142
name={`${id}.numberOfResources`}
143-
initialValue="3"
143+
initialValue={numberOfResources}
144144
>
145145
{({ input, meta }) => (
146146
<NumberInput
@@ -160,7 +160,7 @@ function TeamDetailsModal({ open, onClose, submitForm, addedRoles }) {
160160
<Field
161161
validate={validateExists}
162162
name={`${id}.durationWeeks`}
163-
initialValue="20"
163+
initialValue={durationWeeks}
164164
>
165165
{({ input, meta }) => (
166166
<NumberInput
@@ -177,11 +177,11 @@ function TeamDetailsModal({ open, onClose, submitForm, addedRoles }) {
177177
<Error name={`${id}.durationWeeks`} />
178178
</td>
179179
<td>
180-
{startMonthVisible[id] ? (
180+
{startMonth || startMonthVisible[id] ? (
181181
<>
182182
<Field
183183
name={`${id}.startMonth`}
184-
initialValue={Date.now()}
184+
initialValue={new Date(startMonth).getTime()}
185185
>
186186
{(props) => (
187187
<MonthPicker

‎src/routes/CreateNewTeam/reducers/index.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,27 @@ const reducer = (state = initialState, action) => {
4444
...state,
4545
matchingRole: action.payload,
4646
};
47+
4748
case ACTION_TYPE.DELETE_MATCHING_ROLE:
4849
return {
4950
...state,
5051
matchingRole: null,
5152
};
53+
54+
case ACTION_TYPE.EDIT_MATCHING_ROLE:
55+
const index = _.findIndex(state.addedRoles, {
56+
searchId: action.payload.searchId,
57+
});
58+
state.addedRoles[index] = _.extend(
59+
{},
60+
state.addedRoles[index],
61+
_.omit(action.payload, "searchId")
62+
);
63+
return {
64+
...state,
65+
addedRoles: [...state.addedRoles],
66+
};
67+
5268
case ACTION_TYPE.ADD_SEARCHED_ROLE:
5369
return {
5470
...state,

0 commit comments

Comments
 (0)
This repository has been archived.