diff --git a/src/constants/index.js b/src/constants/index.js index f1c5b67c..f36d2868 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -264,6 +264,7 @@ export const ACTION_TYPE = { */ ADD_MATCHING_ROLE: "ADD_MATCHING_ROLE", DELETE_MATCHING_ROLE: "DELETE_MATCHING_ROLE", + EDIT_MATCHING_ROLE: "EDIT_MATCHING_ROLE", }; /** diff --git a/src/routes/CreateNewTeam/actions/index.js b/src/routes/CreateNewTeam/actions/index.js index 0a224017..62d6e04a 100644 --- a/src/routes/CreateNewTeam/actions/index.js +++ b/src/routes/CreateNewTeam/actions/index.js @@ -36,6 +36,11 @@ const deleteMatchingRole = () => ({ type: ACTION_TYPE.DELETE_MATCHING_ROLE, }); +const editMatchingRole = (role) => ({ + type: ACTION_TYPE.EDIT_MATCHING_ROLE, + payload: role, +}); + export const clearSearchedRoles = () => (dispatch, getState) => { dispatch(clearRoles()); updateLocalStorage(getState().searchedRoles); @@ -51,6 +56,11 @@ export const addRoleSearchId = (id) => (dispatch, getState) => { updateLocalStorage(getState().searchedRoles); }; +export const editRoleAction = (role) => (dispatch, getState) => { + dispatch(editMatchingRole(role)); + updateLocalStorage(getState().searchedRoles); +}; + export const deleteSearchedRole = (id) => (dispatch, getState) => { dispatch(deleteRole(id)); updateLocalStorage(getState().searchedRoles); diff --git a/src/routes/CreateNewTeam/components/EditRoleModal/index.jsx b/src/routes/CreateNewTeam/components/EditRoleModal/index.jsx new file mode 100644 index 00000000..fa8bdedb --- /dev/null +++ b/src/routes/CreateNewTeam/components/EditRoleModal/index.jsx @@ -0,0 +1,165 @@ +/** + * Edit Role Modal + * Popup form to enter details about current role + */ +import React, { useEffect, useState } from "react"; +import PT from "prop-types"; +import { Form, Field, useField } from "react-final-form"; +import FormField from "components/FormField"; +import BaseCreateModal from "../BaseCreateModal"; +import Button from "components/Button"; +import MonthPicker from "components/MonthPicker"; +import InformationTooltip from "components/InformationTooltip"; +import IconCrossLight from "../../../../assets/images/icon-cross-light.svg"; +import "./styles.module.scss"; +import NumberInput from "components/NumberInput"; +import { validator, validateExists, validateMin, composeValidators } from "./utils/validator"; + +const Error = ({ name }) => { + const { + meta: { dirty, error }, + } = useField(name, { subscription: { dirty: true, error: true } }); + return dirty && error ? {error} : null; +}; + +function EditRoleModal({ open, onClose, submitForm, role }) { + const [startMonthVisible, setStartMonthVisible] = useState(false); + + return ( +
{ + changeValue(state, fieldName, () => undefined); + }, + }} + validate={validator} + > + {({ + handleSubmit, + hasValidationErrors, + form: { + mutators: { clearField }, + getState, + }, + }) => { + return ( + + Submit + + } + disableFocusTrap + > +
+ + + + + + + + + + + +
# of resourcesDuration (weeks)Start month
+ + {({ input, meta }) => ( + + )} + + + + + {({ input, meta }) => ( + + )} + + + + {startMonthVisible ? ( + <> + + {(props) => ( + + )} + + + + ) : ( +
+ + +
+ )} +
+
+
+ ); + }} +
+ ); +} + +EditRoleModal.propTypes = { + open: PT.bool, + onClose: PT.func, + submitForm: PT.func, + role: PT.object, +}; + +export default EditRoleModal; diff --git a/src/routes/CreateNewTeam/components/EditRoleModal/styles.module.scss b/src/routes/CreateNewTeam/components/EditRoleModal/styles.module.scss new file mode 100644 index 00000000..4d303148 --- /dev/null +++ b/src/routes/CreateNewTeam/components/EditRoleModal/styles.module.scss @@ -0,0 +1,98 @@ +@import "styles/include"; + +.toggle-button { + @include font-roboto; + outline: none; + border: none; + background: none; + font-size: 12px; + font-weight: 500; + color: #137D60; + padding: 1px 6px 0 6px; + + &.toggle-description { + margin-top: 12px; + > span { + font-size: 18px; + vertical-align: middle; + } + } +} + +.table { + margin-top: 40px; + width: 100%; + th { + @include font-roboto; + font-size: 12px; + color: #555; + padding-bottom: 7px; + border-bottom: 1px solid #d4d4d4; + + &.bold { + font-weight: 700; + } + } + + .role-row { + td { + padding: 18px 18px 18px 0; + vertical-align: top; + @include font-barlow; + font-weight: 600; + font-size: 16px; + color: #2a2a2a; + border-bottom: 1px solid #e9e9e9; + + &:last-child { + padding-right: 0; + } + + input { + @include font-roboto; + font-size: 14px; + line-height: normal; + height: 34px; + &[type="number"] { + width: 98px; + } + } + } + } +} + +.flex-container { + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-start; + width: 118px; + margin-top: 13px; +} + +.error { + font-size: 14px; + font-weight: 400; + color: red; + display: block; +} + +.delete-role { + border: none; + background: none; + + margin-top: 13px; + + &:hover { + g { + stroke: red; + } + } +} + +.modal-body { + overflow-x: auto; + textarea { + height: 95px; + } +} diff --git a/src/routes/CreateNewTeam/components/EditRoleModal/utils/validator.js b/src/routes/CreateNewTeam/components/EditRoleModal/utils/validator.js new file mode 100644 index 00000000..60cb16a8 --- /dev/null +++ b/src/routes/CreateNewTeam/components/EditRoleModal/utils/validator.js @@ -0,0 +1,56 @@ +const composeValidators = (...validators) => (value) => + validators.reduce((error, validator) => error || validator(value), undefined); + +const validateMin = (min) => (value) => + isNaN(value) || value >= min ? undefined : `Should be greater than ${min}`; + +const validateName = (name) => { + if (!name || name.trim().length === 0) { + return "Please enter a team name."; + } + return undefined; +}; + +const validateNumber = (number) => { + const converted = Number(number); + + if ( + Number.isNaN(converted) || + converted !== Math.floor(converted) || + converted < 1 + ) { + return "Please enter a positive integer"; + } + return undefined; +}; + +const validateMonth = (monthString) => { + const then = new Date(monthString); + const now = new Date(); + const thenYear = then.getFullYear(); + const nowYear = now.getFullYear(); + const thenMonth = then.getMonth(); + const nowMonth = now.getMonth(); + + if (thenYear < nowYear || (thenYear === nowYear && thenMonth < nowMonth)) { + return "Start month may not be before current month"; + } + return undefined; +}; + +const validator = (role) => { + const roleErrors = {}; + roleErrors.numberOfResources = validateNumber(role.numberOfResources); + roleErrors.durationWeeks = validateNumber(role.durationWeeks); + if (role.startMonth) { + roleErrors.startMonth = validateMonth(role.startMonth); + } + + return roleErrors; +}; + +const validateExists = (value) => { + return value === undefined ? "Please enter a positive integer" : undefined; +}; + +export { validator, validateExists, validateMin, composeValidators }; diff --git a/src/routes/CreateNewTeam/components/ResultCard/index.jsx b/src/routes/CreateNewTeam/components/ResultCard/index.jsx index 07f21c13..cb56b8ee 100644 --- a/src/routes/CreateNewTeam/components/ResultCard/index.jsx +++ b/src/routes/CreateNewTeam/components/ResultCard/index.jsx @@ -78,10 +78,10 @@ function ResultCard({ role }) { {showRates && !isExternalMember && ( -
+
{userHandle && (

- Hi {userHandle}, we have special rates for you as a Xeno User! + Hi {userHandle}, we have special rates for you as a Wipro User!

)}
@@ -97,8 +97,15 @@ function ResultCard({ role }) {

/Week

-
-

In-Country Rate

+
+

Global Niche Rate

+
+

{formatRate(rates.niche)}

+

/Week

+
+
+
+

Offshore Niche Rate

{formatRate(rates.inCountry)}

/Week

@@ -124,8 +131,15 @@ function ResultCard({ role }) {

/Week

-
-

In-Country Rate

+
+

Global Niche Rate

+
+

{formatRate(rates.rate30Niche)}

+

/Week

+
+
+
+

Offshore Niche Rate

{formatRate(rates.rate30InCountry)}

/Week

@@ -151,8 +165,16 @@ function ResultCard({ role }) {

/Week

-
-

In-Country Rate

+ +
+

Global Niche Rate

+
+

{formatRate(rates.rate20Niche)}

+

/Week

+
+
+
+

Offshore Niche Rate

{formatRate(rates.rate20InCountry)}

/Week

diff --git a/src/routes/CreateNewTeam/components/ResultCard/styles.module.scss b/src/routes/CreateNewTeam/components/ResultCard/styles.module.scss index 6babd0a1..150884c0 100644 --- a/src/routes/CreateNewTeam/components/ResultCard/styles.module.scss +++ b/src/routes/CreateNewTeam/components/ResultCard/styles.module.scss @@ -138,7 +138,7 @@ padding-bottom: 50px; } -.xeno-rates { +.wipro-rates { display: flex; flex-direction: column; padding: 0 25px 50px 52px; @@ -180,7 +180,8 @@ } } .global, - .in-country, + .global-niche, + .offshore-niche, .offshore { display: flex; flex-direction: column; @@ -225,7 +226,12 @@ .global::before { background-color: #c99014; } - .in-country::before { + + .global-niche::before { + background-color: #0ab88a; + } + + .offshore-niche::before { background-color: #716d67; } .offshore::before { diff --git a/src/routes/CreateNewTeam/components/SearchAndSubmit/index.jsx b/src/routes/CreateNewTeam/components/SearchAndSubmit/index.jsx index bfefd418..e24ed940 100644 --- a/src/routes/CreateNewTeam/components/SearchAndSubmit/index.jsx +++ b/src/routes/CreateNewTeam/components/SearchAndSubmit/index.jsx @@ -14,10 +14,13 @@ import InputContainer from "../InputContainer"; import SearchContainer from "../SearchContainer"; import SubmitContainer from "../SubmitContainer"; +const SEARCHINGTIME = 1600; + function SearchAndSubmit(props) { const { stages, setStages, searchObject, onClick, page } = props; const [searchState, setSearchState] = useState(null); + const [isNewRole, setIsNewRole] = useState(false); const { matchingRole } = useSelector((state) => state.searchedRoles); @@ -48,12 +51,14 @@ function SearchAndSubmit(props) { if (previousSearchId) { searchObjectCopy.previousRoleSearchRequestId = previousSearchId; } + const searchingBegin = Date.now(); searchRoles(searchObjectCopy) .then((res) => { const name = _.get(res, "data.name"); const searchId = _.get(res, "data.roleSearchRequestId"); if (name && !isCustomRole({ name })) { - dispatch(addSearchedRole({ searchId, name })); + dispatch(addSearchedRole({ searchId, name, numberOfResources: 1, durationWeeks: 4 })); + setIsNewRole(true) } else if (searchId) { dispatch(addRoleSearchId(searchId)); } @@ -63,8 +68,13 @@ function SearchAndSubmit(props) { console.error(err); }) .finally(() => { - setCurrentStage(2, stages, setStages); - setSearchState("done"); + _.delay( + () => { + setCurrentStage(2, stages, setStages); + setSearchState("done"); + }, + Date.now() - searchingBegin > SEARCHINGTIME ? 0 : 1500 + ); }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [dispatch, previousSearchId, searchObject]); @@ -80,9 +90,11 @@ function SearchAndSubmit(props) { /> { setSearchState("state2"); - }, 800); - }, 800); + }, 500); + }, 500); return () => { clearTimeout(timer1); diff --git a/src/routes/CreateNewTeam/components/SearchContainer/index.jsx b/src/routes/CreateNewTeam/components/SearchContainer/index.jsx index 33f1a580..7b94720c 100644 --- a/src/routes/CreateNewTeam/components/SearchContainer/index.jsx +++ b/src/routes/CreateNewTeam/components/SearchContainer/index.jsx @@ -5,26 +5,48 @@ * search pages. Contains logic and supporting * components for searching for roles. */ -import React, { useCallback, useState } from "react"; +import React, { useCallback, useState, useMemo, useEffect } from "react"; import PT from "prop-types"; +import { useDispatch } from "react-redux"; +import { editRoleAction } from "../../actions"; import AddedRolesAccordion from "../AddedRolesAccordion"; import Completeness from "../Completeness"; import SearchCard from "../SearchCard"; import ResultCard from "../ResultCard"; +import EditRoleModal from '../EditRoleModal' import NoMatchingProfilesResultCard from "../NoMatchingProfilesResultCard"; import { isCustomRole } from "utils/helpers"; import AddAnotherModal from "../AddAnotherModal"; import "./styles.module.scss"; function SearchContainer({ + isNewRole, stages, completenessStyle, navigate, addedRoles, searchState, + previousSearchId, matchingRole, }) { const [addAnotherOpen, setAddAnotherOpen] = useState(false); + const [showEditModal, setShowEditModal] = useState(false); + + const dispatch = useDispatch(); + const currentRole = useMemo(() => { + return _.find(addedRoles, { searchId: previousSearchId }); + }, [addedRoles, previousSearchId]); + + useEffect(() => { + if (isNewRole) { + setShowEditModal(true) + } + }, [isNewRole]); + + const onSubmitEditRole = useCallback((role) => { + setShowEditModal(false) + dispatch(editRoleAction({...role, searchId: previousSearchId})) + }, [addedRoles, previousSearchId]); const onSubmit = useCallback(() => { setAddAnotherOpen(false); @@ -64,6 +86,12 @@ function SearchContainer({ percentage={getPercentage()} />
+ {showEditModal && setShowEditModal(false)} + submitForm={onSubmitEditRole} + />} setAddAnotherOpen(false)} @@ -76,8 +104,10 @@ function SearchContainer({ } SearchContainer.propTypes = { + isNewRole: PT.bool, stages: PT.array, completenessStyle: PT.string, + previousSearchId: PT.string, navigate: PT.func, addedRoles: PT.array, searchState: PT.string, diff --git a/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx b/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx index 30d89b08..0f8743a1 100644 --- a/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx +++ b/src/routes/CreateNewTeam/components/TeamDetailsModal/index.jsx @@ -133,14 +133,14 @@ function TeamDetailsModal({ open, onClose, submitForm, addedRoles }) { Start month - {addedRoles.map(({ searchId: id, name }) => ( + {addedRoles.map(({ searchId: id, name, numberOfResources, durationWeeks, startMonth }) => ( {name} {({ input, meta }) => ( {({ input, meta }) => ( - {startMonthVisible[id] ? ( + {startMonth || startMonthVisible[id] ? ( <> {(props) => ( { ...state, matchingRole: action.payload, }; + case ACTION_TYPE.DELETE_MATCHING_ROLE: return { ...state, matchingRole: null, }; + + case ACTION_TYPE.EDIT_MATCHING_ROLE: + const index = _.findIndex(state.addedRoles, { + searchId: action.payload.searchId, + }); + state.addedRoles[index] = _.extend( + {}, + state.addedRoles[index], + _.omit(action.payload, "searchId") + ); + return { + ...state, + addedRoles: [...state.addedRoles], + }; + case ACTION_TYPE.ADD_SEARCHED_ROLE: return { ...state,