Skip to content

Commit 0b8d124

Browse files
committed
React - utilities, components, interaction
1 parent f309701 commit 0b8d124

File tree

9 files changed

+461
-2
lines changed

9 files changed

+461
-2
lines changed

resources/react/Main.tsx

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,85 @@
11

2-
function Main() {
2+
import { useState } from 'react';
3+
import {getTasks} from "./utils";
4+
import "react-responsive-modal/styles.css";
5+
import { ToastContainer } from 'react-toastify';
6+
import 'react-toastify/dist/ReactToastify.css';
7+
import ModalEdit from "./components/ModalEdit";
8+
import ModalDelete from "./components/ModalDelete";
9+
import TaskList from "./components/TaskList";
10+
import SelectProject from "./components/SelectProject";
11+
import AddTaskForm from "./components/AddTaskForm";
12+
13+
14+
function Main () {
15+
const [projectId, setProjectId] = useState('');
16+
const projectsData = document.getElementById('app').getAttribute('data-projects');
17+
const projects = JSON.parse(projectsData);
18+
const [tasks, setTasks] = useState([]);
19+
const [isModalEditOpen, setIsModalEditOpen] = useState(false);
20+
const [modalEditTask, setModalEditTask] = useState({id: '', title: '', description: ''});
21+
const [isModalDeleteOpen, setIsModalDeleteOpen] = useState(false);
22+
const [modalDeleteTaskId, setModalDeleteTaskId] = useState('');
23+
const [newTask, setNewTask] = useState({title: '', description: ''});
24+
25+
const reloadTasks = () => {
26+
getTasks(projectId).then((tasksData) => setTasks(tasksData));
27+
};
28+
329
return (
430
<div>
5-
<h2 className="test-class">React App</h2>
31+
<ToastContainer autoClose={2000} />
32+
<ModalEdit isModalEditOpen={isModalEditOpen}
33+
setIsModalEditOpen={setIsModalEditOpen}
34+
modalEditTask={modalEditTask}
35+
setModalEditTask={setModalEditTask}
36+
reloadTasks={reloadTasks}
37+
/>
38+
<ModalDelete isModalDeleteOpen={isModalDeleteOpen}
39+
setIsModalDeleteOpen={setIsModalDeleteOpen}
40+
modalDeleteTaskId={modalDeleteTaskId}
41+
reloadTasks={reloadTasks}
42+
/>
43+
<div className="left-side">
44+
{tasks.length > 0 ? (
45+
<TaskList tasks={tasks}
46+
setIsModalEditOpen={setIsModalEditOpen}
47+
setModalEditTask={setModalEditTask}
48+
setIsModalDeleteOpen={setIsModalDeleteOpen}
49+
setModalDeleteTaskId={setModalDeleteTaskId}
50+
projectId={projectId}
51+
setTasks={setTasks}
52+
/>
53+
) : (
54+
<div className="no-tasks">
55+
{projectId === '' ? (
56+
<p>Choose a project to see its tasks.</p>
57+
) : (
58+
<p>This project has no tasks.</p>
59+
)}
60+
</div>
61+
)}
62+
</div>
63+
<div className="right-side">
64+
<div className="right-side-wrapper">
65+
<SelectProject projectId={projectId}
66+
projects={projects}
67+
setProjectId={setProjectId}
68+
setTasks={setTasks}
69+
/>
70+
{projectId === '' ? (
71+
<div className="no-project-selected">
72+
<p>Please select a project to be able to add tasks.</p>
73+
</div>
74+
) : (
75+
<AddTaskForm newTask={newTask}
76+
setNewTask={setNewTask}
77+
projectId={projectId}
78+
reloadTasks={reloadTasks}
79+
/>
80+
)}
81+
</div>
82+
</div>
683
</div>
784
);
885
}

resources/react/axiosConfig.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
import axios from 'axios';
3+
4+
export default axios.create({
5+
baseURL: '/api',
6+
});
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
2+
import {createTask} from "../utils";
3+
4+
5+
function AddTaskForm({
6+
newTask,
7+
setNewTask,
8+
projectId,
9+
reloadTasks
10+
}) {
11+
const clearTaskCreate = () => {
12+
setNewTask({title: '', description: ''});
13+
};
14+
const submitTaskCreate = () => {
15+
createTask(newTask, projectId).then(() => {
16+
setNewTask({title: '', description: ''});
17+
reloadTasks();
18+
});
19+
};
20+
21+
return (
22+
<>
23+
<h2 className="add-task-header">Add Task</h2>
24+
25+
<h3 className="add-task-header">Title</h3>
26+
<input type="text"
27+
className="add-task-input"
28+
onChange={(e) => setNewTask({...newTask, title: e.target.value})}
29+
value={newTask.title} />
30+
<h3 className="add-task-input-header">Description</h3>
31+
<textarea className="add-task-textarea"
32+
onChange={(e) => setNewTask({...newTask, description: e.target.value})}
33+
value={newTask.description || ''} />
34+
35+
<div className="add-task-actions">
36+
<button className="add-task-btn add-task-btn-cancel"
37+
onClick={clearTaskCreate}>Clear
38+
</button>
39+
<button className="add-task-btn add-task-btn-submit"
40+
onClick={submitTaskCreate}>Add</button>
41+
</div>
42+
</>
43+
);
44+
}
45+
46+
export default AddTaskForm;
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
2+
import {Modal} from "react-responsive-modal";
3+
import {deleteTask} from "../utils";
4+
5+
6+
function ModalDelete({
7+
isModalDeleteOpen,
8+
setIsModalDeleteOpen,
9+
modalDeleteTaskId,
10+
reloadTasks
11+
}) {
12+
const submitTaskDelete = () => {
13+
setIsModalDeleteOpen(false);
14+
deleteTask(modalDeleteTaskId).then(() => {
15+
reloadTasks();
16+
});
17+
};
18+
19+
return (
20+
<Modal open={isModalDeleteOpen} onClose={() => setIsModalDeleteOpen(false)} center>
21+
<div className="modal-content">
22+
<h2 className="modal-header">Delete Task</h2>
23+
24+
<p className="modal-question">Are you sure you want to delete this task?</p>
25+
26+
<div className="modal-actions">
27+
<button className="modal-btn modal-btn-cancel"
28+
onClick={() => setIsModalDeleteOpen(false)}
29+
>Cancel</button>
30+
<button className="modal-btn modal-btn-submit"
31+
onClick={submitTaskDelete}
32+
>Yes</button>
33+
</div>
34+
</div>
35+
</Modal>
36+
);
37+
}
38+
39+
export default ModalDelete;
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
2+
import {Modal} from "react-responsive-modal";
3+
import React from "react";
4+
import {editTask} from "../utils";
5+
6+
7+
function ModalEdit({
8+
isModalEditOpen,
9+
setIsModalEditOpen,
10+
modalEditTask,
11+
setModalEditTask,
12+
reloadTasks
13+
}) {
14+
const submitTaskEdit = () => {
15+
setIsModalEditOpen(false);
16+
editTask(modalEditTask).then(() => {
17+
reloadTasks();
18+
});
19+
};
20+
21+
return (
22+
<Modal open={isModalEditOpen} onClose={() => setIsModalEditOpen(false)} center>
23+
<div className="modal-content">
24+
<h2 className="modal-header">Edit Task</h2>
25+
26+
<h3 className="modal-input-header">Title</h3>
27+
<input type="text" value={modalEditTask.title}
28+
className="modal-input"
29+
onChange={(e) => setModalEditTask({...modalEditTask, title: e.target.value})}/>
30+
<h3 className="modal-input-header">Description</h3>
31+
<textarea className="modal-textarea"
32+
onChange={(e) => setModalEditTask({...modalEditTask, description: e.target.value})}
33+
value={modalEditTask.description || ''} />
34+
35+
<div className="modal-actions">
36+
<button className="modal-btn modal-btn-cancel"
37+
onClick={() => setIsModalEditOpen(false)}
38+
>Close
39+
</button>
40+
<button className="modal-btn modal-btn-submit"
41+
onClick={submitTaskEdit}
42+
>Save</button>
43+
</div>
44+
</div>
45+
</Modal>
46+
)
47+
}
48+
49+
export default ModalEdit;
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
2+
import {getTasks} from "../utils";
3+
4+
5+
function SelectProject({
6+
projectId,
7+
projects,
8+
setProjectId,
9+
setTasks
10+
}) {
11+
const selectProject = (e) => {
12+
const value = e.target.value;
13+
setProjectId(value);
14+
if (value === '') {
15+
setTasks([]);
16+
} else {
17+
getTasks(value).then((tasksData) => setTasks(tasksData));
18+
}
19+
};
20+
21+
return (
22+
<div className="projects">
23+
<select className="projects-select"
24+
value={projectId}
25+
onChange={selectProject}>
26+
<option value="" defaultValue>Choose a project</option>
27+
{projects.map((project) => (
28+
<option key={project.id}
29+
value={project.id}>{project.name}</option>
30+
))}
31+
</select>
32+
</div>
33+
);
34+
}
35+
36+
export default SelectProject;

resources/react/components/Task.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
2+
function Task({
3+
task,
4+
setIsModalEditOpen,
5+
setModalEditTask,
6+
setIsModalDeleteOpen,
7+
setModalDeleteTaskId
8+
}) {
9+
const handleEdit = () => {
10+
setModalEditTask(task);
11+
setIsModalEditOpen(true);
12+
};
13+
14+
const handleDelete = () => {
15+
setModalDeleteTaskId(task.id);
16+
setIsModalDeleteOpen(true);
17+
};
18+
19+
return (
20+
<div className="task-item-content">
21+
<span className="task-project">
22+
<span className="task-project-name">{task.project.name}</span>
23+
<span className="task-time">Created {task.created}</span>
24+
</span>
25+
<span className="task-title">{task.title}</span>
26+
<div className="task-actions">
27+
<button className="task-edit-btn" onClick={handleEdit}>Edit</button>
28+
<button className="task-delete-btn" onClick={handleDelete}>Delete</button>
29+
</div>
30+
</div>
31+
);
32+
}
33+
34+
export default Task;
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
2+
import {DragDropContext, Draggable, Droppable} from "react-beautiful-dnd";
3+
import Task from "./Task";
4+
import {reorderTasks} from "../utils";
5+
6+
7+
const getItemStyle = (isDragging, draggableStyle) => ({
8+
background: isDragging ? 'lightgreen' : 'grey',
9+
...draggableStyle,
10+
});
11+
12+
function TaskList({
13+
tasks,
14+
setIsModalEditOpen,
15+
setModalEditTask,
16+
setIsModalDeleteOpen,
17+
setModalDeleteTaskId,
18+
projectId,
19+
setTasks
20+
}) {
21+
const handleDragEnd = (result) => {
22+
if (!result.destination || result.destination.index === result.source.index) {
23+
return;
24+
}
25+
26+
const items = Array.from(tasks);
27+
const [reorderedItem] = items.splice(result.source.index, 1);
28+
items.splice(result.destination.index, 0, reorderedItem);
29+
reorderTasks(projectId, result.source.index + 1, result.destination.index + 1);
30+
31+
setTasks(items);
32+
};
33+
34+
return (
35+
<DragDropContext onDragEnd={handleDragEnd}>
36+
<Droppable droppableId="droppable">
37+
{(provided) => (
38+
<ul {...provided.droppableProps} ref={provided.innerRef}>
39+
{tasks.map((task, index) => (
40+
<Draggable key={task.id.toString()} draggableId={task.id.toString()} index={index}>
41+
{(provided, snapshot) => (
42+
<li ref={provided.innerRef}
43+
{...provided.draggableProps}
44+
{...provided.dragHandleProps}
45+
className="task-item"
46+
style={getItemStyle(snapshot.isDragging, provided.draggableProps.style)}
47+
>
48+
<Task task={task}
49+
setIsModalEditOpen={setIsModalEditOpen}
50+
setModalEditTask={setModalEditTask}
51+
setIsModalDeleteOpen={setIsModalDeleteOpen}
52+
setModalDeleteTaskId={setModalDeleteTaskId}
53+
/>
54+
</li>
55+
)}
56+
</Draggable>
57+
))}
58+
{provided.placeholder}
59+
</ul>
60+
)}
61+
</Droppable>
62+
</DragDropContext>
63+
);
64+
}
65+
66+
export default TaskList;

0 commit comments

Comments
 (0)