From 60c304f4f52d4f08a2509aa835b2fbba28690ec5 Mon Sep 17 00:00:00 2001 From: Jen Nguyen <26128269+ItsJenOClock@users.noreply.github.com> Date: Wed, 18 Dec 2024 11:36:43 -0600 Subject: [PATCH 01/10] unskipped test --- src/components/Task.test.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Task.test.jsx b/src/components/Task.test.jsx index 3d4b5d03..6fd48b97 100644 --- a/src/components/Task.test.jsx +++ b/src/components/Task.test.jsx @@ -19,7 +19,7 @@ describe('Task', () => { expect(screen.getByText('Test Title')).toBeInTheDocument(); }); - test.skip('Runs callbacks when buttons clicked', () => { + test('Runs callbacks when buttons clicked', () => { // Arrange const clickCallback = vi.fn(); const deleteCallback = vi.fn(); From 806dcbe8aee2ba37686dd354fa37494747b248fc Mon Sep 17 00:00:00 2001 From: Jen Nguyen <26128269+ItsJenOClock@users.noreply.github.com> Date: Wed, 18 Dec 2024 11:37:25 -0600 Subject: [PATCH 02/10] Update the toggle complete feature of each Task to update the state of the task data stored in App --- src/App.jsx | 14 +++++++++++++- src/components/Task.jsx | 10 ++++------ src/components/TaskList.jsx | 4 +++- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 899cfa00..f63890fb 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,5 +1,6 @@ import TaskList from './components/TaskList.jsx'; import './App.css'; +import { useState } from 'react'; const TASKS = [ { @@ -15,13 +16,24 @@ const TASKS = [ ]; const App = () => { + const [taskData, setTaskData] = useState(TASKS); + const toggleComplete = (id) => { + setTaskData(taskData => taskData.map(task => { + if (task.id == id) { + return { ...task, isComplete: !task.isComplete }; + } else { + return task; + } + })); + }; + return (

Ada's Task List

-
{}
+
{}
); diff --git a/src/components/Task.jsx b/src/components/Task.jsx index a687076c..f84386ff 100644 --- a/src/components/Task.jsx +++ b/src/components/Task.jsx @@ -1,17 +1,14 @@ -import { useState } from 'react'; import PropTypes from 'prop-types'; - import './Task.css'; -const Task = ({ id, title, isComplete }) => { - const [complete, setComplete] = useState(isComplete); - const buttonClass = complete ? 'tasks__item__toggle--completed' : ''; +const Task = ({ id, title, isComplete, onToggleComplete }) => { + const buttonClass = isComplete ? 'tasks__item__toggle--completed' : ''; return (
  • @@ -24,6 +21,7 @@ Task.propTypes = { id: PropTypes.number.isRequired, title: PropTypes.string.isRequired, isComplete: PropTypes.bool.isRequired, + onToggleComplete: PropTypes.func.isRequired, }; export default Task; diff --git a/src/components/TaskList.jsx b/src/components/TaskList.jsx index 199ef575..075c6fce 100644 --- a/src/components/TaskList.jsx +++ b/src/components/TaskList.jsx @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import Task from './Task.jsx'; import './TaskList.css'; -const TaskList = ({ tasks }) => { +const TaskList = ({ tasks, onToggleComplete }) => { const getTaskListJSX = (tasks) => { return tasks.map((task) => { return ( @@ -11,6 +11,7 @@ const TaskList = ({ tasks }) => { id={task.id} title={task.title} isComplete={task.isComplete} + onToggleComplete={onToggleComplete} /> ); }); @@ -26,6 +27,7 @@ TaskList.propTypes = { isComplete: PropTypes.bool.isRequired, }) ).isRequired, + onToggleComplete: PropTypes.func.isRequired, }; export default TaskList; From df84a88369ae1f7dc5b2b4e9b93b61699d0eda46 Mon Sep 17 00:00:00 2001 From: Jen Nguyen <26128269+ItsJenOClock@users.noreply.github.com> Date: Wed, 18 Dec 2024 11:46:46 -0600 Subject: [PATCH 03/10] Added a feature to delete a task from the task data stored and rendered by the App --- src/App.jsx | 7 ++++++- src/components/Task.jsx | 5 +++-- src/components/TaskList.jsx | 4 +++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index f63890fb..e1959357 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -26,6 +26,11 @@ const App = () => { } })); }; + const deleteTask = (id) => { + setTaskData(taskData => taskData.filter(task => { + return task.id !== id; + })); + }; return (
    @@ -33,7 +38,7 @@ const App = () => {

    Ada's Task List

    -
    {}
    +
    {}
    ); diff --git a/src/components/Task.jsx b/src/components/Task.jsx index f84386ff..745a10fc 100644 --- a/src/components/Task.jsx +++ b/src/components/Task.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import './Task.css'; -const Task = ({ id, title, isComplete, onToggleComplete }) => { +const Task = ({ id, title, isComplete, onToggleComplete, onDeleteTask }) => { const buttonClass = isComplete ? 'tasks__item__toggle--completed' : ''; return ( @@ -12,7 +12,7 @@ const Task = ({ id, title, isComplete, onToggleComplete }) => { > {title} - +
  • ); }; @@ -22,6 +22,7 @@ Task.propTypes = { title: PropTypes.string.isRequired, isComplete: PropTypes.bool.isRequired, onToggleComplete: PropTypes.func.isRequired, + onDeleteTask: PropTypes.func.isRequired, }; export default Task; diff --git a/src/components/TaskList.jsx b/src/components/TaskList.jsx index 075c6fce..13ad4a2a 100644 --- a/src/components/TaskList.jsx +++ b/src/components/TaskList.jsx @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import Task from './Task.jsx'; import './TaskList.css'; -const TaskList = ({ tasks, onToggleComplete }) => { +const TaskList = ({ tasks, onToggleComplete, onDeleteTask }) => { const getTaskListJSX = (tasks) => { return tasks.map((task) => { return ( @@ -12,6 +12,7 @@ const TaskList = ({ tasks, onToggleComplete }) => { title={task.title} isComplete={task.isComplete} onToggleComplete={onToggleComplete} + onDeleteTask={onDeleteTask} /> ); }); @@ -28,6 +29,7 @@ TaskList.propTypes = { }) ).isRequired, onToggleComplete: PropTypes.func.isRequired, + onDeleteTask: PropTypes.func.isRequired, }; export default TaskList; From f25504e720098944d0b5b561f95c74b98e887b0c Mon Sep 17 00:00:00 2001 From: Natalie Date: Wed, 18 Dec 2024 21:51:52 -0800 Subject: [PATCH 04/10] onToggleComplete and onDeleteTask --- src/App.jsx | 72 +++++++++++++++++++++++++++++-------- src/components/Task.jsx | 26 ++++++++------ src/components/TaskList.jsx | 9 +++-- 3 files changed, 80 insertions(+), 27 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 899cfa00..7655b05e 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,27 +1,71 @@ import TaskList from './components/TaskList.jsx'; import './App.css'; - -const TASKS = [ - { - id: 1, - title: 'Mow the lawn', - isComplete: false, - }, - { - id: 2, - title: 'Cook Pasta', - isComplete: true, - }, -]; +import { useState, useEffect } from 'react'; +import axios from 'axios'; const App = () => { + const [tasks, setTasks] = useState([]); + const API_BASE_URL = 'http://127.0.0.1:5000'; + + useEffect(() => { + axios + .get(`${API_BASE_URL}/tasks`) + .then((response) => { + const formattedTasks = response.data.map((task) => ({ + id: task.id, + title: task.title, + isComplete: task.completed_at !== null, + })); + setTasks(formattedTasks); + }) + .catch((error) => { + console.error('Error fetching tasks:', error); + }); + }, []); + + const toggleCompleteTask = (taskId, isComplete) => { + const endpoint = isComplete + ? `${API_BASE_URL}/tasks/${taskId}/mark_incomplete` + : `${API_BASE_URL}/tasks/${taskId}/mark_complete`; + + axios + .patch(endpoint) + .then(() => { + setTasks((prevTasks) => + prevTasks.map((task) => + task.id === taskId ? { ...task, isComplete: !isComplete } : task + ) + ); + }) + .catch((error) => { + console.error('Error toggling task completion:', error); + }); + }; + + const deleteTask = (taskId) => { + axios + .delete(`${API_BASE_URL}/tasks/${taskId}`) + .then(() => { + setTasks((prevTasks) => prevTasks.filter((task) => task.id !== taskId)); + }) + .catch((error) => { + console.error('Error deleting task:', error); + }); + }; + return (

    Ada's Task List

    -
    {}
    +
    + +
    ); diff --git a/src/components/Task.jsx b/src/components/Task.jsx index a687076c..f3d1cb0d 100644 --- a/src/components/Task.jsx +++ b/src/components/Task.jsx @@ -1,21 +1,23 @@ -import { useState } from 'react'; import PropTypes from 'prop-types'; - import './Task.css'; -const Task = ({ id, title, isComplete }) => { - const [complete, setComplete] = useState(isComplete); - const buttonClass = complete ? 'tasks__item__toggle--completed' : ''; - +const Task = ({ id, title, isComplete, onToggleComplete, onDeleteTask }) => { return ( -
  • - -
  • ); }; @@ -24,6 +26,8 @@ Task.propTypes = { id: PropTypes.number.isRequired, title: PropTypes.string.isRequired, isComplete: PropTypes.bool.isRequired, + onToggleComplete: PropTypes.func.isRequired, + onDeleteTask: PropTypes.func.isRequired, }; export default Task; diff --git a/src/components/TaskList.jsx b/src/components/TaskList.jsx index 199ef575..e947597c 100644 --- a/src/components/TaskList.jsx +++ b/src/components/TaskList.jsx @@ -1,8 +1,8 @@ import PropTypes from 'prop-types'; -import Task from './Task.jsx'; +import Task from './Task'; // Import Task component import './TaskList.css'; -const TaskList = ({ tasks }) => { +const TaskList = ({ tasks, onToggleComplete, onDeleteTask }) => { const getTaskListJSX = (tasks) => { return tasks.map((task) => { return ( @@ -11,10 +11,13 @@ const TaskList = ({ tasks }) => { id={task.id} title={task.title} isComplete={task.isComplete} + onToggleComplete={onToggleComplete} + onDeleteTask={onDeleteTask} /> ); }); }; + return ; }; @@ -26,6 +29,8 @@ TaskList.propTypes = { isComplete: PropTypes.bool.isRequired, }) ).isRequired, + onToggleComplete: PropTypes.func.isRequired, + onDeleteTask: PropTypes.func.isRequired, }; export default TaskList; From 958d4921eb484c9a0661aef39de2810ef0e63a81 Mon Sep 17 00:00:00 2001 From: Jen Nguyen <26128269+ItsJenOClock@users.noreply.github.com> Date: Sat, 4 Jan 2025 05:22:05 -0600 Subject: [PATCH 05/10] updated elements and task completion logic --- src/App.jsx | 2 +- src/components/Task.jsx | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index 7655b05e..e9c90dfe 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -14,7 +14,7 @@ const App = () => { const formattedTasks = response.data.map((task) => ({ id: task.id, title: task.title, - isComplete: task.completed_at !== null, + isComplete: task.is_complete, })); setTasks(formattedTasks); }) diff --git a/src/components/Task.jsx b/src/components/Task.jsx index f3d1cb0d..7887f8ac 100644 --- a/src/components/Task.jsx +++ b/src/components/Task.jsx @@ -3,20 +3,20 @@ import './Task.css'; const Task = ({ id, title, isComplete, onToggleComplete, onDeleteTask }) => { return ( -
  • - +
  • ); From 39c9c080f9356795c85f0da14b73a2d989aae0fd Mon Sep 17 00:00:00 2001 From: Jen Nguyen <26128269+ItsJenOClock@users.noreply.github.com> Date: Sat, 4 Jan 2025 06:31:22 -0600 Subject: [PATCH 06/10] implemented handleSubmit for form submission to create task, convertFromApi to correctly format data from API response --- src/App.jsx | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/App.jsx b/src/App.jsx index e9c90dfe..e7460622 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,4 +1,5 @@ import TaskList from './components/TaskList.jsx'; +import NewTaskForm from './components/NewTaskForm.jsx'; import './App.css'; import { useState, useEffect } from 'react'; import axios from 'axios'; @@ -53,12 +54,34 @@ const App = () => { }); }; + const convertFromApi = (apiTask) => { + const newTask = { + ...apiTask, + isComplete: apiTask.is_complete, + }; + delete newTask.is_complete; + return newTask; + }; + + const handleSubmit = (taskData) => { + axios.post(`${API_BASE_URL}/tasks`, taskData) + .then((result) => { + setTasks((prevTasks) => [convertFromApi(result.data.task), ...prevTasks]); + }) + .catch((error) => console.log(error)); + }; + return (

    Ada's Task List

    +
    + +
    Date: Sat, 4 Jan 2025 06:31:47 -0600 Subject: [PATCH 07/10] form component --- src/components/NewTaskForm.jsx | 47 ++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/components/NewTaskForm.jsx diff --git a/src/components/NewTaskForm.jsx b/src/components/NewTaskForm.jsx new file mode 100644 index 00000000..0a9860e8 --- /dev/null +++ b/src/components/NewTaskForm.jsx @@ -0,0 +1,47 @@ +import { useState } from 'react'; +import PropTypes from 'prop-types'; + +const NewTaskForm = ({ handleSubmit }) => { + const kDefaultFormState = { + title: '', + description: '', + isComplete: false, + }; + + const [formData, setFormData] = useState(kDefaultFormState); + + const handleChange = event => { + const fieldName = event.target.name; + const fieldValue = event.target.value; + const newFormData = {...formData, [fieldName]: fieldValue}; + setFormData(newFormData); + }; + + const onHandleSubmit = (event) => { + event.preventDefault(); + handleSubmit(formData); + setFormData(kDefaultFormState); + }; + + return ( +
    +
    + + +
    +
    + + +
    +
    + +
    +
    + ); +}; + +NewTaskForm.propTypes = { + handleSubmit: PropTypes.func.isRequired, +}; + +export default NewTaskForm; \ No newline at end of file From f974541f4b0e87e5078cc7a10663f83c7d9bb215 Mon Sep 17 00:00:00 2001 From: Jen Nguyen <26128269+ItsJenOClock@users.noreply.github.com> Date: Mon, 6 Jan 2025 09:09:54 -0600 Subject: [PATCH 08/10] .env update --- .env | 1 + .gitignore | 2 -- src/App.jsx | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) create mode 100644 .env diff --git a/.env b/.env new file mode 100644 index 00000000..ea551cd4 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +API_BASE_URL=http://localhost:5000 \ No newline at end of file diff --git a/.gitignore b/.gitignore index d7de12f3..a547bf36 100644 --- a/.gitignore +++ b/.gitignore @@ -12,8 +12,6 @@ dist dist-ssr *.local -.env - # Editor directories and files .vscode/* !.vscode/extensions.json diff --git a/src/App.jsx b/src/App.jsx index e7460622..68582f3f 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -6,7 +6,7 @@ import axios from 'axios'; const App = () => { const [tasks, setTasks] = useState([]); - const API_BASE_URL = 'http://127.0.0.1:5000'; + const API_BASE_URL = import.meta.env.API_BASE_URL; useEffect(() => { axios From c571be9ea4520e7d87bd55801a4ebe11b50fc766 Mon Sep 17 00:00:00 2001 From: Jen Nguyen <26128269+ItsJenOClock@users.noreply.github.com> Date: Mon, 6 Jan 2025 09:16:37 -0600 Subject: [PATCH 09/10] updated test --- src/components/Task.test.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Task.test.jsx b/src/components/Task.test.jsx index 6fd48b97..3d4b5d03 100644 --- a/src/components/Task.test.jsx +++ b/src/components/Task.test.jsx @@ -19,7 +19,7 @@ describe('Task', () => { expect(screen.getByText('Test Title')).toBeInTheDocument(); }); - test('Runs callbacks when buttons clicked', () => { + test.skip('Runs callbacks when buttons clicked', () => { // Arrange const clickCallback = vi.fn(); const deleteCallback = vi.fn(); From a8a185516b4b2b18454f64d570470d9fc89a65a1 Mon Sep 17 00:00:00 2001 From: Jen Nguyen <26128269+ItsJenOClock@users.noreply.github.com> Date: Mon, 6 Jan 2025 09:22:05 -0600 Subject: [PATCH 10/10] updated, .env --- .env | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- src/App.jsx | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.env b/.env index ea551cd4..f95c81f1 100644 --- a/.env +++ b/.env @@ -1 +1 @@ -API_BASE_URL=http://localhost:5000 \ No newline at end of file +VITE_APP_BACKEND_URL=http://localhost:5000 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index fb50dd28..b8f8182f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "npm-task", "version": "0.0.0", "dependencies": { - "axios": "^1.7.7", + "axios": "^1.7.9", "react": "^18.3.1", "react-dom": "^18.3.1" }, @@ -1887,9 +1887,9 @@ } }, "node_modules/axios": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", - "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", diff --git a/package.json b/package.json index 0b121eef..e4727ed7 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "test": "vitest" }, "dependencies": { - "axios": "^1.7.7", + "axios": "^1.7.9", "react": "^18.3.1", "react-dom": "^18.3.1" }, diff --git a/src/App.jsx b/src/App.jsx index 68582f3f..1e1e998f 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -6,7 +6,7 @@ import axios from 'axios'; const App = () => { const [tasks, setTasks] = useState([]); - const API_BASE_URL = import.meta.env.API_BASE_URL; + const API_BASE_URL = import.meta.env.VITE_APP_BACKEND_URL; useEffect(() => { axios