Skip to content

Commit 1664ae8

Browse files
Merge pull request #3531 from OneCommunityGlobal/newell-refactor-tasks
Newell - cleanup and rewrote tasks component
2 parents 4d4a779 + 592bca4 commit 1664ae8

13 files changed

Lines changed: 509 additions & 244 deletions

File tree

.eslintignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ src/components/SummaryManagement/**
2222
src/components/TeamMemberTasks/**
2323
src/components/Teams/TeamMembersPopup.jsx
2424
src/components/UserManagement/**
25-
src/components/UserProfile/**
25+
src/components/UserProfile/**
26+
src/components/Announcements/index.jsx

src/components/Projects/WBS/WBSDetail/AddTask/AddTaskModal.jsx

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import {
1515
} from '../../../../../languages/en/messages.js';
1616
import 'react-day-picker/lib/style.css';
1717
import '../../../../Header/DarkMode.css';
18-
import TagsSearch from '../components/TagsSearch';
18+
import UserSearch from '../EditTask/UserSearch';
19+
import UserTag from '../EditTask/UserTag';
1920
import './AddTaskModal.css';
2021

2122
function AddTaskModal(props) {
@@ -24,6 +25,7 @@ function AddTaskModal(props) {
2425
*/
2526
// props from store
2627
const { tasks, copiedTask, allMembers, allProjects, error, darkMode } = props;
28+
const label = props.label ?? 'Add Task';
2729

2830
const TINY_MCE_INIT_OPTIONS = {
2931
license_key: 'gpl',
@@ -426,16 +428,17 @@ function AddTaskModal(props) {
426428
<div className="add_new_task_form-group">
427429
<span className={`add_new_task_form-label ${fontColor}`}>Resources</span>
428430
<span className="add_new_task_form-input_area">
429-
<TagsSearch
430-
placeholder="Add resources"
431-
// modified below to check if allMembers is undefined before applying filter
432-
members={allMembers ? allMembers.filter(user => user.isActive) : false}
433-
addResources={addResources}
434-
removeResource={removeResource}
435-
resourceItems={resourceItems}
436-
disableInput={false}
437-
darkMode={darkMode}
438-
/>
431+
<UserSearch addedUsers={resourceItems} onAddUser={addResources} />
432+
<div className="d-flex flex-wrap align-items-start justify-content-start">
433+
{resourceItems?.map((user) => (
434+
<ul
435+
key={`${user.name}`}
436+
className="d-flex align-items-start justify-content-start m-0 p-1"
437+
>
438+
<UserTag userName={user.name} userId={user.userID} onRemoveUser={removeResource} />
439+
</ul>
440+
))}
441+
</div>
439442
</span>
440443
</div>
441444
<div className="add_new_task_form-group">
@@ -790,15 +793,14 @@ function AddTaskModal(props) {
790793
onClick={openModal}
791794
style={darkMode ? boxStyleDark : boxStyle}
792795
>
793-
Add Task
796+
{ label }
794797
</Button>
795798
</>
796799
);
797800
}
798801

799802
const mapStateToProps = state => ({
800803
tasks: state.tasks.taskItems,
801-
copiedTask: state.tasks.copiedTask,
802804
allMembers: state.projectMembers.members,
803805
allProjects: state.allProjects,
804806
error: state.tasks.error,

src/components/Projects/WBS/WBSDetail/ControllerRow/ControllerRow.jsx

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
moveTasks,
1414
deleteTask,
1515
emptyTaskItems,
16-
copyTask,
1716
deleteChildrenTasks,
1817
} from '../../../../../actions/task.js';
1918
import ModalDelete from './../../../../../components/common/Modal';
@@ -39,6 +38,7 @@ function ControllerRow(props) {
3938
const [isCopied, setIsCopied] = useState(false);
4039
const [modalDelete, setModalDelete] = useState(false);
4140

41+
const { currentTask, copyCurrentTask } = props;
4242
/*
4343
* -------------------------------- functions --------------------------------
4444
*/
@@ -50,23 +50,19 @@ function ControllerRow(props) {
5050
const toggle = () => setDropdownOpen(prevState => !prevState);
5151

5252
const onMove = async (from, to) => {
53-
props.setIsLoading(true);
5453
await props.moveTasks(props.wbsId, from, to);
5554
props.load();
56-
props.setIsLoading(false);
5755
};
5856

59-
const onCopy = id => {
57+
const onCopy = () => {
6058
setIsCopied(true);
61-
props.copyTask(id);
59+
copyCurrentTask(currentTask);
6260
};
6361

6462
const deleteTask = async (taskId, mother) => {
65-
props.setIsLoading(true);
6663
await props.deleteTask(taskId, mother);
6764
await props.emptyTaskItems();
6865
await props.load();
69-
props.setIsLoading(false);
7066
};
7167

7268
/*
@@ -78,6 +74,7 @@ function ControllerRow(props) {
7874
<div className="task-action-buttons">
7975
{props.level < 4 && canPostTask ? (
8076
<AddTaskModal
77+
label={"Add Subtask"}
8178
key={`addTask_${props.taskId}`}
8279
taskNum={props.num}
8380
taskId={props.taskId}
@@ -107,7 +104,6 @@ function ControllerRow(props) {
107104
mother={props.mother}
108105
level={props.level}
109106
load={props.load}
110-
setIsLoading={props.setIsLoading}
111107
/>
112108

113109
{canDeleteTask && (
@@ -175,7 +171,6 @@ export default connect(mapStateToProps, {
175171
moveTasks,
176172
deleteTask,
177173
emptyTaskItems,
178-
copyTask,
179174
getPopupById,
180175
deleteChildrenTasks,
181176
hasPermission,

src/components/Projects/WBS/WBSDetail/EditTask/EditTaskModal.jsx

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import axios from 'axios';
1616
import { ENDPOINTS } from 'utils/URL';
1717
import { boxStyle, boxStyleDark } from 'styles';
1818
import { toast } from 'react-toastify';
19-
import TagsSearch from '../components/TagsSearch';
19+
import UserSearch from './UserSearch';
20+
import UserTag from './UserTag';
2021
import ReadOnlySectionWrapper from './ReadOnlySectionWrapper';
2122
import '../../../../Header/DarkMode.css'
2223
import '../wbs.css'
@@ -26,7 +27,7 @@ function EditTaskModal(props) {
2627
* -------------------------------- variable declarations --------------------------------
2728
*/
2829
// props from store
29-
const { allMembers, error, darkMode } = props;
30+
const { /* allMembers, */ error, darkMode } = props;
3031

3132
// permissions
3233
const canUpdateTask = props.hasPermission('updateTask');
@@ -218,7 +219,6 @@ function EditTaskModal(props) {
218219
const updateTaskDirectly = (currentMode === "Edit");
219220
console.log({canSuggestTask, canUpdateTask, updateTaskDirectly});
220221

221-
props.setIsLoading?.(true);
222222
await props.updateTask(
223223
props.taskId,
224224
updatedTask,
@@ -227,7 +227,6 @@ function EditTaskModal(props) {
227227
);
228228
props.setTask?.(updatedTask);
229229
await props.load?.();
230-
props.setIsLoading?.(false);
231230

232231
if (error === 'none' || Object.keys(error).length === 0) {
233232
toggle();
@@ -346,15 +345,17 @@ function EditTaskModal(props) {
346345
<td id="edit-modal-td" scope="col">Resources</td>
347346
<td id="edit-modal-td" scope="col">
348347
<div>
349-
<TagsSearch
350-
placeholder="Add resources"
351-
members={allMembers.filter(user=>user.isActive)}
352-
addResources={editable? addResources : () => {}}
353-
removeResource={editable? removeResource : () => {}}
354-
resourceItems={resourceItems}
355-
disableInput={!editable}
356-
darkMode={darkMode}
357-
/>
348+
<UserSearch addedUsers={resourceItems} onAddUser={editable ? addResources : () => {}} />
349+
<div className="d-flex flex-wrap align-items-start justify-content-start">
350+
{resourceItems?.map((user) => (
351+
<ul
352+
key={`${user.name}`}
353+
className="d-flex align-items-start justify-content-start m-0 p-1"
354+
>
355+
<UserTag userName={user.name} userId={user.userID} onRemoveUser={editable ? removeResource : () => {}} />
356+
</ul>
357+
))}
358+
</div>
358359
</div>
359360
</td>
360361
</tr>
@@ -771,7 +772,7 @@ function EditTaskModal(props) {
771772
};
772773

773774
const mapStateToProps = state => ({
774-
allMembers: state.projectMembers.members,
775+
// allMembers: state.projectMembers.members,
775776
error: state.tasks.error,
776777
darkMode: state.theme.darkMode,
777778
});
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import React, { useState } from 'react';
2+
import { FormControl } from 'react-bootstrap';
3+
import ListGroup from 'react-bootstrap/ListGroup';
4+
import { useUserSearch } from './useUserSearch';
5+
6+
function UserSearch({ addedUsers, onAddUser }) {
7+
const [searchTerm, setSearchTerm] = useState('');
8+
const [show, setShow] = useState(false);
9+
10+
const { users: results, loading, error } = useUserSearch(searchTerm);
11+
12+
const handleInputChange = (event) => {
13+
setSearchTerm(event.target.value);
14+
};
15+
16+
const isSelected = (id) => addedUsers.find(user => user.userID === id);
17+
18+
return (
19+
<div
20+
style={{ position: 'relative', width: '250px' }}
21+
tabIndex={-1}
22+
onFocus={() => setShow(true)}
23+
onBlur={() => setShow(false)}
24+
>
25+
<FormControl
26+
type="text"
27+
placeholder="Search users..."
28+
value={searchTerm}
29+
onChange={handleInputChange}
30+
/>
31+
32+
{show && (
33+
<ListGroup
34+
style={{
35+
position: 'absolute',
36+
top: '100%',
37+
width: '100%',
38+
zIndex: 2000,
39+
maxHeight: '200px',
40+
overflowY: 'auto',
41+
}}
42+
>
43+
{loading && (
44+
<ListGroup.Item disabled>Loading...</ListGroup.Item>
45+
)}
46+
47+
{error && (
48+
<ListGroup.Item disabled style={{ color: 'red' }}>
49+
{error.message}
50+
</ListGroup.Item>
51+
)}
52+
53+
{!loading && results.length === 0 && searchTerm.trim().length > 1 && (
54+
<ListGroup.Item disabled>No users found</ListGroup.Item>
55+
)}
56+
{results.map(({ firstName, lastName, _id }) => {
57+
const selected = isSelected(_id);
58+
59+
return (
60+
<ListGroup.Item
61+
action
62+
key={`user-${_id}`}
63+
disabled={selected}
64+
onMouseDown={() => {
65+
if (!selected) {
66+
onAddUser(_id, firstName, lastName);
67+
}
68+
}}
69+
style={{
70+
backgroundColor: selected ? '#f8f9fa' : undefined,
71+
color: selected ? '#6c757d' : undefined,
72+
cursor: selected ? 'not-allowed' : 'pointer',
73+
}}
74+
>
75+
{`${firstName} ${lastName}`}
76+
</ListGroup.Item>
77+
)
78+
})}
79+
</ListGroup>
80+
)}
81+
</div>
82+
);
83+
}
84+
85+
export default UserSearch;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React from 'react';
2+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3+
import { faTimesCircle } from '@fortawesome/free-solid-svg-icons';
4+
5+
function UserTag({ userId, userName, onRemoveUser }) {
6+
return (
7+
<li
8+
className="rounded-pill badge bg-primary text-wrap"
9+
onClick={() => onRemoveUser(userId)}
10+
>
11+
<div className="text-white">
12+
<small className="fs-6 mr-1">{`${userName}`}</small>
13+
<FontAwesomeIcon icon={faTimesCircle} />
14+
</div>
15+
</li>
16+
);
17+
}
18+
19+
export default UserTag;
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { useState, useEffect } from 'react';
2+
import axios from 'axios';
3+
4+
import { ENDPOINTS } from '../../../../../utils/URL'; // adjust path
5+
6+
/**
7+
* Custom hook to search users by query string with debounce.
8+
* @param {string} query - The search query.
9+
* @param {number} delay - Debounce delay in ms (default: 350)
10+
* @returns {{ users: Array<Object>, loading: boolean, error: Error|null }}
11+
*/
12+
export function useUserSearch(query, delay = 350) {
13+
const [users, setUsers] = useState([]);
14+
const [loading, setLoading] = useState(false);
15+
const [error, setError] = useState(null);
16+
const [debouncedQuery, setDebouncedQuery] = useState(query);
17+
18+
useEffect(() => {
19+
const handler = setTimeout(() => {
20+
setDebouncedQuery(query);
21+
}, delay);
22+
23+
return () => clearTimeout(handler);
24+
}, [query, delay]);
25+
26+
useEffect(() => {
27+
if (!debouncedQuery || debouncedQuery.trim().length < 2) {
28+
setUsers([]);
29+
return;
30+
}
31+
32+
let isCancelled = false;
33+
34+
async function search() {
35+
setLoading(true);
36+
setError(null);
37+
try {
38+
const res = await axios.get(`${ENDPOINTS.SEARCH_USER}?name=${debouncedQuery}`);
39+
if (!isCancelled) {
40+
setUsers(res.data);
41+
}
42+
} catch (err) {
43+
if (!isCancelled) {
44+
setError(err);
45+
}
46+
} finally {
47+
if (!isCancelled) {
48+
setLoading(false);
49+
}
50+
}
51+
}
52+
53+
search();
54+
55+
return () => {
56+
isCancelled = true;
57+
};
58+
}, [debouncedQuery]);
59+
60+
return { users, loading, error };
61+
}

0 commit comments

Comments
 (0)