Skip to content
Open
Show file tree
Hide file tree
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
58 changes: 54 additions & 4 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ input {
margin-right: 10px;
}

select {
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
margin-right: 10px;
}

button {
padding: 10px 20px;
border: none;
Expand Down Expand Up @@ -58,7 +65,6 @@ li {

li span {
flex: 1;
margin-right: 10px;
}

li span.completed {
Expand All @@ -68,16 +74,39 @@ li span.completed {

.filter-buttons {
display: flex;
justify-content: center;
align-items: center;
justify-content: space-between;
}

.status-filter {
display: flex;
gap: 10px;
}

.user-filter {
display: flex;
align-items: center;
margin-left: 20px;
}

.user-filter label {
margin-right: 10px;
}

.task-item {
display: flex;
flex-direction: row;
justify-content: center;
flex-direction: column;
}

.task-item-header {
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
}

.task-item-header .caret {
margin-left: 5px;
}

.task-item-buttons {
Expand All @@ -86,3 +115,24 @@ li span.completed {
margin-left: auto;
align-items: center;
}

.subtasks {
padding-left: 10px;
margin-top: 10px;
}

.subtask-item {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 5px;
flex-direction: row;
}

.subtask-input-container {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
flex-direction: row;
}
26 changes: 22 additions & 4 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,53 @@ function App() {
name: "Complete TPS reports",
completed: false,
addedBy: { id: 1, name: "Kevin" },
subtasks: [],
},
{
name: "Reconcile Dunder Mifflin accounts",
completed: false,
addedBy: { id: 2, name: "Angela" },
subtasks: [],
},
{
name: "Review Peter's timesheet",
completed: true,
addedBy: { id: 1, name: "Oscar" },
addedBy: { id: 3, name: "Oscar" },
subtasks: [],
},
{
name: "Party Planning Committee expense reports",
completed: false,
addedBy: { id: 2, name: "Angela" },
subtasks: [],
},
]);
const [filter, setFilter] = useState("all");
const [userFilter, setUserFilter] = useState("all");

const users = tasks.reduce((users, task) => {
return users.includes(task.addedBy) ? users : [...users, task.addedBy];
}, []);

return (
<div>
<Header />
<div className="App">
<div className="content">
<TaskForm setTasks={setTasks} />
<TaskForm setTasks={setTasks} users={users} />
</div>
<div className="content">
<FilterButtons setFilter={setFilter} />
<TaskList tasks={tasks} filter={filter} setTasks={setTasks} />
<FilterButtons
setFilter={setFilter}
setUserFilter={setUserFilter}
users={users}
/>
<TaskList
tasks={tasks}
filter={filter}
userFilter={userFilter}
setTasks={setTasks}
/>
</div>
</div>
</div>
Expand Down
15 changes: 11 additions & 4 deletions src/components/FilterButtons.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import React from "react";
import UserFilter from "./UserFilter";

function FilterButtons({ setFilter }) {
function FilterButtons({ setFilter, setUserFilter, users }) {
return (
<div className="filter-buttons">
<button onClick={() => setFilter("all")}>All</button>
<button onClick={() => setFilter("completed")}>Completed</button>
<button onClick={() => setFilter("incomplete")}>Incomplete</button>
<div className="status-filter">
<button onClick={() => setFilter("all")}>All</button>
<button onClick={() => setFilter("completed")}>Completed</button>
<button onClick={() => setFilter("incomplete")}>Incomplete</button>
</div>
<UserFilter
setUserFilter={(e) => setUserFilter(e.target.value)}
users={users}
/>
</div>
);
}
Expand Down
16 changes: 14 additions & 2 deletions src/components/TaskForm.jsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
import React, { useState } from "react";

function TaskForm({ setTasks }) {
function TaskForm({ setTasks, users }) {
const [taskName, setTaskName] = useState("");
const [selectedUser, setSelectedUser] = useState(users[0].id);

const handleSubmit = (e) => {
e.preventDefault();
const user = users.find((user) => user.id === selectedUser);
setTasks((prevTasks) => [
...prevTasks,
{ name: taskName, completed: false },
{ name: taskName, completed: false, subtasks: [], addedBy: user },
]);
setTaskName("");
};

return (
<form onSubmit={handleSubmit}>
<select
onChange={(e) => setSelectedUser(parseInt(e.target.value, 10))}
value={selectedUser}
>
{users.map((user) => (
<option key={user.id} value={user.id}>
{user.name}
</option>
))}
</select>
<input
type="text"
value={taskName}
Expand Down
88 changes: 75 additions & 13 deletions src/components/TaskItem.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import React from "react";
import React, { useState, useEffect, useRef } from "react";

function TaskItem({ task, index, setTasks }) {
const toggleCompletion = () => {
const [showSubtasks, setShowSubtasks] = useState(false);
const [newSubtask, setNewSubtask] = useState("");
const taskItemRef = useRef(null);

const toggeCompletion = () => {
setTasks((prevTasks) =>
prevTasks.map((t, i) =>
i === index ? { ...t, completed: !t.completed } : t
i === index ? { ...t, completed: !t.complted } : t
)
);
};
Expand All @@ -13,17 +17,75 @@ function TaskItem({ task, index, setTasks }) {
setTasks((prevTasks) => prevTasks.filter((_, i) => i !== index));
};

const addSubtask = () => {
const newSubtaskObject = {
name: newSubtask,
completed: false,
subtasks: [],
addedBy: task.addedBy,
};
task.subtasks.push(newSubtaskObject);
setTasks((prevTasks) =>
prevTasks.map((t, i) =>
i === index ? { ...t, subtasks: task.subtasks } : t
)
);
setNewSubtask("");
};

const deleteSubtask = (subtaskIndex) => {
const updatedSubtasks = task.subtasks.filter((_, i) => i !== subtaskIndex);
setTasks((prevTasks) =>
prevTasks.map((t, i) =>
i === index ? { ...t, subtasks: updatedSubtasks } : t
)
);
};

useEffect(() => {
const currentRef = taskItemRef.current;
currentRef.addEventListener("click", () => setShowSubtasks(!showSubtasks));
}, []);

return (
<li className="task-item">
<div className="task-item-header">
<span className={task.completed ? "completed" : ""}>{task.name}</span>
</div>
<div className="task-item-buttons">
<button onClick={toggleCompletion}>
{task.completed ? "Undo" : "Complete"}
</button>
<button onClick={deleteTask}>Delete</button>
</div>
<li className="task-item" ref={taskItemRef}>
<span className="task-item-header">
<span className={task.completed ? "completed" : undefined}>
{task.name} (Added by: {task.addedBy.name})
</span>
<div className="task-item-buttons">
<button onClick={toggeCompletion}>
{task.completed ? "Undo" : "Complete"}
</button>
<button onClick={deleteTask}>Delete</button>
<div className="caret">{showSubtasks ? "▼" : "▶"}</div>
</div>
</span>
{showSubtasks && (
<ul className="subtasks">
<div className="subtask-input-container">
<input
type="text"
value={newSubtask}
onChange={(e) => setNewSubtask(e.target.value)}
placeholder="Enter subtask"
onKeyDown={(e) => {
if (e.key === "Enter") {
addSubtask();
}
}}
/>
<button onClick={addSubtask}>Add Subtask</button>
</div>
{task.subtasks &&
task.subtasks.map((subtask, subIndex) => (
<li key={subIndex} className="subtask-item">
<span>{subtask.name}</span>
<button onClick={deleteSubtask(subIndex)}>Delete</button>
</li>
))}
</ul>
)}
</li>
);
}
Expand Down
23 changes: 23 additions & 0 deletions src/components/TaskItem.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from "react";
import { render, fireEvent } from "@testing-library/react";
import TaskItem from "./TaskItem";

test("it should add a new subtask", () => {
const { getByPlaceholderText, getByText } = render(
<TaskItem
task={{
name: "Test Task",
completed: false,
subtasks: [],
}}
index={0}
setTasks={() => {}}
/>
);

const taskItem = getByText("▶");
fireEvent.click(taskItem);
const input = getByPlaceholderText("Enter subtask");
fireEvent.change(input, { target: { value: "New Subtask" } });
fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
});
13 changes: 6 additions & 7 deletions src/components/TaskList.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import React from "react";
import TaskItem from "./TaskItem";

function TaskList({ tasks, filter, setTasks }) {
const filteredTasks = tasks.filter((task) =>
filter === "all"
? true
: filter === "completed"
? task.completed
: !task.completed
function TaskList({ tasks, filter, userFilter, setTasks }) {
const filteredTasks = tasks.filter(
(task) =>
(filter === "all" ||
(filter === "completed" ? task.completed : !task.completed)) &&
(userFilter === "all" || task.addedBy.id === parseInt(userFilter, 10))
);

return (
Expand Down
17 changes: 17 additions & 0 deletions src/components/UserFilter.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from "react";

function UserFilter({ setUserFilter, users }) {
return (
<div className="user-filter">
<label>Filter by user:</label>
<select onChange={setUserFilter}>
<option value="all">All</option>
{users.map((user) => (
<option value={user.id}>{user.name}</option>
))}
</select>
</div>
);
}

export default UserFilter;