Skip to content

Commit

Permalink
feat(Project): assign all members to task (#100)
Browse files Browse the repository at this point in the history
* feat(Project): assign all members to task
* fix(Projects): truncate large assignees list in ViewTaskModal

---------

Co-authored-by: Ethan Turner <[email protected]>
  • Loading branch information
jakeaturner and ethanaturner authored Jul 17, 2023
1 parent 445836f commit 1a7f4ea
Show file tree
Hide file tree
Showing 5 changed files with 274 additions and 3 deletions.
79 changes: 77 additions & 2 deletions client/src/components/projects/ProjectView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ import AddTaskDependencyModal from './TaskComponents/AddTaskDependencyModal';
import RemoveTaskAssigneeModal from './TaskComponents/RemoveTaskAssigneeModal';
import AddTaskAssigneeModal from './TaskComponents/AddTaskAssigneeModal';
import ViewTaskModal from './TaskComponents/ViewTaskModal';
import AssignAllModal from './TaskComponents/AssignAllModal';
const ManageTeamModal = lazy(() => import('./ManageTeamModal'));

const ProjectView = (props) => {
Expand Down Expand Up @@ -220,6 +221,11 @@ const ProjectView = (props) => {
const [atdTasks, setATDTasks] = useState([]);
const [atdError, setATDError] = useState(false);

// Assign All to Task Modal
const [showAssignAllModal, setShowAssignAllModal] = useState(false);
const [assignAllLoading, setAssignAllLoading] = useState(false);
const [assignAllError, setAssignAllError] = useState(false);
const [assignAllSubtasks, setAssignAllSubtasks] = useState(false);

// Remove Task Dependency Modal
const [showRTDModal, setShowRTDModal] = useState(false);
Expand Down Expand Up @@ -1258,6 +1264,43 @@ const ProjectView = (props) => {
} else setATAError(true);
};

const submitAssignAllMembersToTask = async () => {
try {
setAssignAllError(false);
if (!viewTaskData || !viewTaskData.taskID || isEmptyString(viewTaskData.taskID)) {
setAssignAllError(false);
return;
}

setAssignAllLoading(true);
const assignAllRes = await axios.put("/project/task/assignees/add-all", {
projectID: props.match.params.id,
taskID: viewTaskData.taskID,
subtasks: assignAllSubtasks,
});

if (!assignAllRes.data.err) {
getProjectTasks();
setShowAssignAllModal(false);
setAssignAllSubtasks(false);
setAssignAllError(false);
closeViewTaskModal();
} else {
handleGlobalError(assignAllRes.data.errMsg);
}
} catch (err) {
handleGlobalError(err);
} finally {
setAssignAllLoading(false);
}
};

const handleCloseAssignAllModal = () => {
setShowAssignAllModal(false);
setAssignAllSubtasks(false);
setAssignAllError(false);
}

const openRMTAModal = (name, uuid) => {
if (viewTaskData.taskID !== null
&& !isEmptyString(viewTaskData.taskID)
Expand Down Expand Up @@ -2348,7 +2391,7 @@ const ProjectView = (props) => {
<div className='right-flex'>
<div className='task-assignees-row'>
{(item.hasOwnProperty('assignees') && item.assignees.length > 0) &&
(item.assignees.map((assignee, assignIdx) => {
(item.assignees.slice(0,5).map((assignee, assignIdx) => {
if (assignee.uuid && assignee.firstName && assignee.lastName) {
return (
<Popup
Expand All @@ -2368,6 +2411,11 @@ const ProjectView = (props) => {
} else return null;
}))
}
{
(item.hasOwnProperty('assignees') && item.assignees.length > 5) && (
<p className='muted-text'> + {item.assignees.length - 5} more</p>
)
}
</div>
<Popup
content={<span className='color-semanticred'><em>Delete Task</em></span>}
Expand Down Expand Up @@ -2423,7 +2471,7 @@ const ProjectView = (props) => {
<div className='right-flex'>
<div className='task-assignees-row'>
{(subtask.hasOwnProperty('assignees') && subtask.assignees.length > 0) &&
(subtask.assignees.map((assignee, assignIdx) => {
(subtask.assignees.slice(0, 5).map((assignee, assignIdx) => {
if (assignee.uuid && assignee.firstName && assignee.lastName) {
return (
<Popup
Expand All @@ -2443,6 +2491,20 @@ const ProjectView = (props) => {
} else return null;
}))
}
{
(subtask.hasOwnProperty('assignees') && subtask.assignees.length > 5) && (
<Popup
key='more-subtask-assigneed'
trigger={
<p className='muted-text'> + {subtask.assignees.length - 5} more</p>
}
header={<span><strong>More assignees</strong></span>}
position='top center'
/>

)
}

</div>
<Popup
content={<span className='color-semanticred'><em>Delete Subtask</em></span>}
Expand Down Expand Up @@ -2966,6 +3028,7 @@ const ProjectView = (props) => {
openRTDModal={(depend) => openRTDModal(depend)}
openRMTAModal={(name, uuid) => openRMTAModal(name, uuid)}
openManageTaskModal={(mode, taskID, parent) => openManageTaskModal(mode, taskID, parent)}
openAssignAllModal={() => setShowAssignAllModal(true)}
atdLoading={atdLoading}
getTaskMessages={getTaskMessages}
getParentTaskName={(id) => getParentTaskName(id)}
Expand All @@ -2990,6 +3053,18 @@ const ProjectView = (props) => {
onRequestAdd={submitAddTaskAssignee}
onClose={closeATAModal}
/>
<AssignAllModal
show={showAssignAllModal}
viewTaskData={viewTaskData}
assignAllError={assignAllError}
assignAllLoading={assignAllLoading}
assignAllSubtasks={assignAllSubtasks}
setAssignAllSubtasks={(newVal) => setAssignAllSubtasks(newVal)}
openViewTaskModal={(id) => openViewTaskModal(id)}
getParentTaskName={(id) => getParentTaskName(id)}
onRequestAssignAll={submitAssignAllMembersToTask}
onClose={handleCloseAssignAllModal}
/>
{/* Remove Task Assignee Modal */}
<RemoveTaskAssigneeModal
show={showRMTAModal}
Expand Down
84 changes: 84 additions & 0 deletions client/src/components/projects/TaskComponents/AssignAllModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Modal, Breadcrumb, Button, Checkbox, Icon } from "semantic-ui-react";

interface AssignAllModalProps {
show: boolean;
viewTaskData: any;
assignAllError: boolean;
assignAllLoading: boolean;
assignAllSubtasks: boolean;
setAssignAllSubtasks: (newVal: boolean) => void;
openViewTaskModal: (id: string) => void;
getParentTaskName: (id: string) => string;
onRequestAssignAll: () => void;
onClose: () => void;
}

const AssignAllModal: React.FC<AssignAllModalProps> = ({
show,
viewTaskData,
assignAllError,
assignAllLoading,
assignAllSubtasks,
setAssignAllSubtasks,
openViewTaskModal,
getParentTaskName,
onRequestAssignAll,
onClose,
}) => {
return (
<Modal open={show} onClose={onClose} size="small">
<Modal.Header>
<Breadcrumb className="task-view-header-crumbs">
{viewTaskData.parent && viewTaskData.parent !== "" && (
<>
<Breadcrumb.Section
onClick={() => openViewTaskModal(viewTaskData.parent)}
active
>
{getParentTaskName(viewTaskData.parent)}
</Breadcrumb.Section>
<Breadcrumb.Divider icon="right chevron" />
</>
)}
<Breadcrumb.Section active={viewTaskData.parent ? false : true}>
<em>
{viewTaskData.hasOwnProperty("title")
? viewTaskData.title
: "Loading..."}
</em>
: Assign All Members to Task
</Breadcrumb.Section>
</Breadcrumb>
</Modal.Header>
<Modal.Content scrolling>
<p>
Are you sure you want to assign all members of this project to{" "}
<em>
{viewTaskData.hasOwnProperty("title")
? viewTaskData.title
: "Loading..."}
</em>
?
</p>
{(!viewTaskData.parent || viewTaskData.parent === "") && (
<Checkbox
toggle
checked={assignAllSubtasks}
onChange={(_e, data) => setAssignAllSubtasks(data.checked ?? false)}
label="Assign to all subtasks"
className="mb-2p"
/>
)}
</Modal.Content>
<Modal.Actions>
<Button onClick={onClose}>Cancel</Button>
<Button color="green" onClick={onRequestAssignAll}>
<Icon name="add" />
Assign All
</Button>
</Modal.Actions>
</Modal>
);
};

export default AssignAllModal;
28 changes: 27 additions & 1 deletion client/src/components/projects/TaskComponents/ViewTaskModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ interface ViewTaskModalProps {
openATDModal: () => void;
openRTDModal: (id: string) => void;
openRMTAModal: (name: string, uuid: string) => void;
openAssignAllModal: () => void;
openManageTaskModal: (
mode: string,
taskID: string | null,
Expand Down Expand Up @@ -86,6 +87,7 @@ const ViewTaskModal: React.FC<ViewTaskModalProps> = ({
openRTDModal,
openRMTAModal,
openManageTaskModal,
openAssignAllModal,
atdLoading,
getTaskMessages,
getParentTaskName,
Expand Down Expand Up @@ -274,7 +276,7 @@ const ViewTaskModal: React.FC<ViewTaskModalProps> = ({
<div className="flex-row-div left-flex">
{viewTaskData.hasOwnProperty("assignees") &&
viewTaskData.assignees.length > 0 &&
viewTaskData.assignees.map((item: any, idx: number) => {
viewTaskData.assignees.slice(0, 5).map((item: any, idx: number) => {
return (
<Popup
key={idx}
Expand Down Expand Up @@ -304,6 +306,11 @@ const ViewTaskModal: React.FC<ViewTaskModalProps> = ({
/>
);
})}
{
(viewTaskData.hasOwnProperty('assignees') && viewTaskData.assignees.length > 5) && (
<p className='muted-text'> + {viewTaskData.assignees.length - 5} more</p>
)
}
<Popup
key="add-assignee"
trigger={
Expand All @@ -323,6 +330,25 @@ const ViewTaskModal: React.FC<ViewTaskModalProps> = ({
}
position="top center"
/>
<Popup
key="assign-all"
trigger={
<Button
size="tiny"
circular
icon="users"
color="blue"
onClick={() => openAssignAllModal()}
disabled={!userProjectMember}
/>
}
header={
<span>
<em>Assign All Members</em>
</span>
}
position="top center"
/>
</div>
</div>
<div className="task-actions-div">
Expand Down
8 changes: 8 additions & 0 deletions server/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -1096,6 +1096,14 @@ router.route('/project/task/assignees/add').put(
tasksAPI.addTaskAssignee,
);

router.route('/project/task/assignees/add-all').put(
authAPI.verifyRequest,
authAPI.getUserAttributes,
tasksAPI.validate('assignAllToTask'),
middleware.checkValidationErrors,
tasksAPI.assignAllMembersToTask,
);

router.route('/project/task/assignees/remove').put(
authAPI.verifyRequest,
authAPI.getUserAttributes,
Expand Down
Loading

0 comments on commit 1a7f4ea

Please sign in to comment.