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

Feature/member management Add Members Directly #107

Merged
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
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -81,7 +81,8 @@
"redux": "^4.0.5",
"redux-logger": "^3.0.6",
"redux-promise-middleware": "^6.1.2",
"redux-thunk": "^2.3.0"
"redux-thunk": "^2.3.0",
"tc-auth-lib": "topcoder-platform/tc-auth-lib#1.0.4"
},
"browserslist": [
"last 1 version",
13 changes: 11 additions & 2 deletions src/components/BaseModal/index.jsx
Original file line number Diff line number Diff line change
@@ -27,14 +27,22 @@ const containerStyle = {
padding: "10px",
};

function BaseModal({ open, onClose, children, title, button, disabled }) {
function BaseModal({
open,
onClose,
children,
title,
button,
disabled,
extraModalStyle,
}) {
return (
<Modal
open={open}
onClose={onClose}
closeIcon={<IconCross width="15px" height="15px" />}
styles={{
modal: modalStyle,
modal: { ...modalStyle, ...extraModalStyle },
modalContainer: containerStyle,
}}
center={true}
@@ -63,6 +71,7 @@ BaseModal.propTypes = {
title: PT.string,
button: PT.element,
disabled: PT.bool,
extraModalStyle: PT.object,
};

export default BaseModal;
2 changes: 1 addition & 1 deletion src/components/DateInput/index.jsx
Original file line number Diff line number Diff line change
@@ -31,7 +31,7 @@ DateInput.propTypes = {
placeholder: PT.string,
onBlur: PT.func,
onFocus: PT.func,
className: PT.string
className: PT.string,
};

export default DateInput;
3 changes: 1 addition & 2 deletions src/components/DateInput/styles.module.scss
Original file line number Diff line number Diff line change
@@ -5,10 +5,9 @@
width: 100%;
}
&.error {
input{
input {
border-color: #fe665d;
}

}
}

8 changes: 4 additions & 4 deletions src/components/FormField/index.jsx
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@ const FormField = ({ field }) => {
<Field name={field.name}>
{({ input, meta }) => (
<div>
{ !field.readonly && (
{!field.readonly && (
<label
styleName={
(input.value != "undefined" &&
@@ -89,9 +89,9 @@ const FormField = ({ field }) => {
onFocus={input.onFocus}
/>
)}
{(field.isRequired || field.customValidator) && meta.error && meta.touched && (
<div styleName="field-error">{meta.error}</div>
)}
{(field.isRequired || field.customValidator) &&
meta.error &&
meta.touched && <div styleName="field-error">{meta.error}</div>}
</div>
)}
</Field>
45 changes: 33 additions & 12 deletions src/components/ReactSelect/index.jsx
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@
import React from "react";
import PT from "prop-types";
import Select from "react-select";
import CreatableSelect from "react-select/creatable";
import "./styles.module.scss";

const ReactSelect = (props) => {
@@ -69,18 +70,36 @@ const ReactSelect = (props) => {

return (
<div styleName="select-wrapper">
<Select
value={props.value}
styles={customStyles}
onChange={props.onChange}
options={props.options}
styleName={props.error ? "error" : ""}
isMulti={props.isMulti}
onBlur={props.onBlur}
onFocus={props.onFocus}
placeholder={props.placeholder}
onInputChange={props.onInputChange}
/>
{props.isCreatable ? (
<CreatableSelect
value={props.value}
styles={customStyles}
onChange={props.onChange}
options={props.options}
styleName={props.error ? "error" : ""}
isMulti={props.isMulti}
onBlur={props.onBlur}
onFocus={props.onFocus}
placeholder={props.placeholder}
onInputChange={props.onInputChange}
noOptionsMessage={() => props.noOptionsText}
createOptionPosition="first"
/>
) : (
<Select
value={props.value}
styles={customStyles}
onChange={props.onChange}
options={props.options}
styleName={props.error ? "error" : ""}
isMulti={props.isMulti}
onBlur={props.onBlur}
onFocus={props.onFocus}
placeholder={props.placeholder}
onInputChange={props.onInputChange}
noOptionsMessage={() => props.noOptionsText}
/>
)}
</div>
);
};
@@ -100,6 +119,8 @@ ReactSelect.propTypes = {
label: PT.string.isRequired,
}).isRequired
),
isCreatable: PT.bool,
noOptionsText: PT.string,
};

export default ReactSelect;
4 changes: 1 addition & 3 deletions src/components/TCForm/index.jsx
Original file line number Diff line number Diff line change
@@ -58,9 +58,7 @@ const TCForm = ({
<div styleName="field-group">
{row.fields.map((field) => (
<div styleName="field-group-field" key={field.name}>
<FormField
field={fields[field]}
/>
<FormField field={fields[field]} />
</div>
))}
</div>
2 changes: 1 addition & 1 deletion src/components/TCForm/styles.module.scss
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@
}
}

.job-form-fields-wrapper{
.job-form-fields-wrapper {
width: 100%;
max-width: 640px;
margin: 0 auto;
2 changes: 1 addition & 1 deletion src/components/TextInput/index.jsx
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@ import "./styles.module.scss";
function TextInput(props) {
return (
<input
styleName={cn("TextInput", props.className, {"readonly": props.readonly})}
styleName={cn("TextInput", props.className, { readonly: props.readonly })}
maxLength={props.maxLength}
min={props.minValue}
onChange={(event) => {
27 changes: 27 additions & 0 deletions src/constants/index.js
Original file line number Diff line number Diff line change
@@ -222,3 +222,30 @@ export const STATUS_OPTIONS = [
{ value: "closed", label: "closed" },
{ value: "cancelled", label: "cancelled" },
];

/*
* TopCoder user roles
*/
export const ROLE_TOPCODER_USER = "Topcoder User";
export const ROLE_CONNECT_COPILOT = "Connect Copilot";
export const ROLE_CONNECT_MANAGER = "Connect Manager";
export const ROLE_CONNECT_ACCOUNT_MANAGER = "Connect Account Manager";
export const ROLE_CONNECT_ADMIN = "Connect Admin";
export const ROLE_ADMINISTRATOR = "administrator";
export const ROLE_CONNECT_COPILOT_MANAGER = "Connect Copilot Manager";
export const ROLE_BUSINESS_DEVELOPMENT_REPRESENTATIVE =
"Business Development Representative";
export const ROLE_PRESALES = "Presales";
export const ROLE_ACCOUNT_EXECUTIVE = "Account Executive";
export const ROLE_PROGRAM_MANAGER = "Program Manager";
export const ROLE_SOLUTION_ARCHITECT = "Solution Architect";
export const ROLE_PROJECT_MANAGER = "Project Manager";

// User roles that can see suggestions when adding new members to project
export const SEE_SUGGESTION_ROLES = [
ROLE_ADMINISTRATOR,
ROLE_CONNECT_ADMIN,
ROLE_CONNECT_MANAGER,
ROLE_CONNECT_ACCOUNT_MANAGER,
ROLE_CONNECT_COPILOT_MANAGER,
];
11 changes: 8 additions & 3 deletions src/routes/JobDetails/index.jsx
Original file line number Diff line number Diff line change
@@ -36,8 +36,10 @@ const JobDetails = ({ teamId, jobId }) => {
const skill = _.find(skills, { id: skillId });

if (!skill) {
console.warn(`Couldn't find name for skill id "${skillId}" of the job "${job.id}".`)
return null
console.warn(
`Couldn't find name for skill id "${skillId}" of the job "${job.id}".`
);
return null;
}

return skill.name;
@@ -79,7 +81,10 @@ const JobDetails = ({ teamId, jobId }) => {
<DataItem title="Resource Type" icon={<IconDescription />}>
{job.resourceType}
</DataItem>
<DataItem title="Resource Rate Frequency" icon={<IconDescription />}>
<DataItem
title="Resource Rate Frequency"
icon={<IconDescription />}
>
{job.rateType}
</DataItem>
<DataItem title="Workload" icon={<IconDescription />}>
29 changes: 15 additions & 14 deletions src/routes/JobForm/index.jsx
Original file line number Diff line number Diff line change
@@ -61,20 +61,21 @@ const JobForm = ({ teamId, jobId }) => {

// as we are using `PUT` method (not `PATCH`) we have send ALL the fields
// fields which we don't send would become `null` otherwise
const getRequestData = (values) => _.pick(values, [
'projectId',
'externalId',
'description',
'title',
'startDate',
'duration',
'numPositions',
'resourceType',
'rateType',
'workload',
'skills',
'status',
]);
const getRequestData = (values) =>
_.pick(values, [
"projectId",
"externalId",
"description",
"title",
"startDate",
"duration",
"numPositions",
"resourceType",
"rateType",
"workload",
"skills",
"status",
]);

useEffect(() => {
if (skills && job && !options) {
14 changes: 10 additions & 4 deletions src/routes/PositionDetails/index.jsx
Original file line number Diff line number Diff line change
@@ -17,7 +17,9 @@ import "./styles.module.scss";

const PositionDetails = ({ teamId, positionId }) => {
// be dafault show "Interested" tab
const [candidateStatus, setCandidateStatus] = useState(CANDIDATE_STATUS.SHORTLIST);
const [candidateStatus, setCandidateStatus] = useState(
CANDIDATE_STATUS.SHORTLIST
);
const {
state: { position, error },
updateCandidate,
@@ -32,10 +34,14 @@ const PositionDetails = ({ teamId, positionId }) => {

// if there are some candidates to review, then show "To Review" tab by default
useEffect(() => {
if (position && _.filter(position.candidates, { status: CANDIDATE_STATUS.OPEN }).length > 0) {
setCandidateStatus(CANDIDATE_STATUS.OPEN)
if (
position &&
_.filter(position.candidates, { status: CANDIDATE_STATUS.OPEN }).length >
0
) {
setCandidateStatus(CANDIDATE_STATUS.OPEN);
}
}, [position])
}, [position]);

return (
<Page title="Job Details">
56 changes: 30 additions & 26 deletions src/routes/ResourceBookingDetails/index.jsx
Original file line number Diff line number Diff line change
@@ -20,26 +20,30 @@ import ResourceDetails from "./ResourceDetails";
import "./styles.module.scss";

const ResourceBookingDetails = ({ teamId, resourceBookingId }) => {
const [resource, loadingError] = useData(getReourceBookingById, resourceBookingId);
const [resource, loadingError] = useData(
getReourceBookingById,
resourceBookingId
);
const [team, loadingTeamError] = useData(getTeamById, teamId);
const [jobTitle, setJobTitle] = useState("")
const [member, setMember] = useState("")
const [jobTitle, setJobTitle] = useState("");
const [member, setMember] = useState("");

useEffect(() => {
if (team) {
const resourceWithMemberDetails = _.find(
team.resources,
{ id: resourceBookingId }
);
const resourceWithMemberDetails = _.find(team.resources, {
id: resourceBookingId,
});

// resource inside Team object has all the member details we need
setMember(resourceWithMemberDetails);

if (resourceWithMemberDetails.jobId) {
const job = _.find(team.jobs, { id: resourceWithMemberDetails.jobId });
setJobTitle(_.get(job, "title", `<Not Found> ${resourceWithMemberDetails.jobId}`));
setJobTitle(
_.get(job, "title", `<Not Found> ${resourceWithMemberDetails.jobId}`)
);
} else {
setJobTitle("<Not Assigned>")
setJobTitle("<Not Assigned>");
}
}
}, [team, resourceBookingId]);
@@ -49,25 +53,25 @@ const ResourceBookingDetails = ({ teamId, resourceBookingId }) => {
{!(member && resource) ? (
<LoadingIndicator error={loadingError || loadingTeamError} />
) : (
<>
<PageHeader
title="Member Details"
backTo={`/taas/myteams/${teamId}`}
/>
<div styleName="content-wrapper">
<ResourceSummary member={member} />
<ResourceDetails resource={resource} jobTitle={jobTitle} />
<div styleName="actions">
<Button
size="medium"
routeTo={`/taas/myteams/${teamId}/rb/${resource.id}/edit`}
>
Edit Member Details
<>
<PageHeader
title="Member Details"
backTo={`/taas/myteams/${teamId}`}
/>
<div styleName="content-wrapper">
<ResourceSummary member={member} />
<ResourceDetails resource={resource} jobTitle={jobTitle} />
<div styleName="actions">
<Button
size="medium"
routeTo={`/taas/myteams/${teamId}/rb/${resource.id}/edit`}
>
Edit Member Details
</Button>
</div>
</div>
</>
)}
</div>
</>
)}
</Page>
);
};
Loading