From b2cd1f0b69f927880fcf3aa96b94daba52a7754f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20M=C3=BCller?= Date: Mon, 3 Mar 2025 22:00:28 -0300 Subject: [PATCH 1/5] Correct filter validation, the validation was reversed. Little ajust on the input, this was a react structure error, when setting value to an initial null controlled component --- src/components/TaskManager.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/TaskManager.tsx b/src/components/TaskManager.tsx index 7b280e8..72883c6 100644 --- a/src/components/TaskManager.tsx +++ b/src/components/TaskManager.tsx @@ -8,12 +8,12 @@ const TaskManager = () => { { id: 2, title: "Clean the house", completed: true }, ]); const [filter, setFilter] = useState("all"); - const [newTask, setNewTask] = useState(); + const [newTask, setNewTask] = useState(""); // Intentional bug: The filter conditions are reversed. const filteredTasks = tasks.filter((task) => { - if (filter === "completed") return task.completed === false; - if (filter === "pending") return task.completed === true; + if (filter === "completed") return task.completed === true; + if (filter === "pending") return task.completed === false; return true; }); From eeab357cea7064637ae2e75b4f24431fb4964c00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20M=C3=BCller?= Date: Tue, 4 Mar 2025 01:14:15 -0300 Subject: [PATCH 2/5] Create store to manipulate Localstorage and save the list on it. Corrects new id rule on add task. Rewrite the deleteTask and toggleTaskCompletion functions to immeddiately update. Instanciate task interface to organize typescript code. Move menu to a new component, change to use UL -> LI and create MenuItem component too. --- src/App.tsx | 4 +- src/components/Menu.tsx | 28 ++++++++++ src/components/MenuItem..tsx | 10 ++++ src/components/TaskItem.tsx | 11 ++-- src/components/TaskManager.tsx | 87 ++++++++++++++++++++++---------- src/interfaces/task.interface.ts | 7 +++ src/store/task.ts | 12 +++++ 7 files changed, 122 insertions(+), 37 deletions(-) create mode 100644 src/components/Menu.tsx create mode 100644 src/components/MenuItem..tsx create mode 100644 src/interfaces/task.interface.ts create mode 100644 src/store/task.ts diff --git a/src/App.tsx b/src/App.tsx index fcd2850..7377da4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,9 +4,9 @@ import TaskManager from "./components/TaskManager"; function App() { return ( -
+
-

Task Manager

+

Task Manager

diff --git a/src/components/Menu.tsx b/src/components/Menu.tsx new file mode 100644 index 0000000..239f9a9 --- /dev/null +++ b/src/components/Menu.tsx @@ -0,0 +1,28 @@ +import React from "react"; +import MenuItem from "./MenuItem."; + +const Menu = ({ setFilter }: any) => { + return ( + + ); +}; + +export default Menu; \ No newline at end of file diff --git a/src/components/MenuItem..tsx b/src/components/MenuItem..tsx new file mode 100644 index 0000000..b8feba8 --- /dev/null +++ b/src/components/MenuItem..tsx @@ -0,0 +1,10 @@ +import React from "react"; + + +const MenuItem = ({ value, textValue, setFilter }: any) => { + return ( +
  • setFilter(value)} id={value + "Button"}>{textValue}
  • + ); +}; + +export default MenuItem; \ No newline at end of file diff --git a/src/components/TaskItem.tsx b/src/components/TaskItem.tsx index 6c2a176..a6f6b94 100644 --- a/src/components/TaskItem.tsx +++ b/src/components/TaskItem.tsx @@ -2,11 +2,11 @@ import React from "react"; const TaskItem = ({ task, onDelete, onToggle }: any) => { return ( -
  • +
  • onToggle(task.id)} className={`cursor-pointer ${ - task.isCompleted ? "text-black" : "line-through text-green-500" + task.completed ? "line-through text-green-500" : "text-black" }`} > {task.title} @@ -14,12 +14,7 @@ const TaskItem = ({ task, onDelete, onToggle }: any) => { diff --git a/src/components/TaskManager.tsx b/src/components/TaskManager.tsx index 72883c6..2641f2e 100644 --- a/src/components/TaskManager.tsx +++ b/src/components/TaskManager.tsx @@ -1,17 +1,36 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import TaskItem from "./TaskItem"; +import Task from "../interfaces/task.interface"; +import Menu from "./Menu"; +import { getFromLocalStorage, setOnLocalStorage } from "../store/task"; const TaskManager = () => { - const [tasks, setTasks] = useState([ - { id: 1, title: "Buy groceries", completed: false }, - { id: 2, title: "Clean the house", completed: true }, - ]); + const [tasks, setTasks] = useState([]); const [filter, setFilter] = useState("all"); const [newTask, setNewTask] = useState(""); - // Intentional bug: The filter conditions are reversed. + // Get tasks from the local storage on the page mount + useEffect(() => { + setTasks(getFromLocalStorage()); + }, []); + + // Update the state list (not have to go to the localstorage every time to get data) and the localStorage. + const updateTasksList = (newList: Task[]) => { + setTasks(newList); + setOnLocalStorage(newList); + } + + const setSelectedFilters = () => { + const filters = document.querySelectorAll('.filter-button'); + filters.forEach(f => { + if(f.id === filter + 'Button') f.classList.add('border-b-2', 'border-b-white', 'font-bold'); + else f.classList.remove('border-b-2', 'border-b-white', 'font-bold'); + }); + }; + const filteredTasks = tasks.filter((task) => { + setSelectedFilters(); if (filter === "completed") return task.completed === true; if (filter === "pending") return task.completed === false; return true; @@ -20,58 +39,72 @@ const TaskManager = () => { const handleAddTask = (e: React.FormEvent) => { e.preventDefault(); if (newTask!.trim() === "") return; - const newTaskObj = { - id: tasks.length + 1, - name: newTask, + const newTaskObj: Task = { + id: tasks.reduce((max, item) => Math.max(max, item.id), 0) + 1, + title: newTask, completed: false, }; - setTasks([...tasks, newTaskObj]); + updateTasksList([...tasks, newTaskObj]); setNewTask(""); }; - // Intentional bug: Directly mutating the tasks array when deleting. const handleDeleteTask = (id: number) => { - const index = tasks.findIndex((task) => task.id === id); - if (index !== -1) { - tasks.splice(index, 1); - setTasks(tasks); - } + const updatedTasks = tasks.filter((item) => item.id !== id); + updateTasksList(updatedTasks); }; const toggleTaskCompletion = (id: number) => { - const task = tasks.find((task) => task.id === id); - - task.isCompleted = !task.isCompleted; + //Update function to get new tasks list to update and use states; + const updatedTasks = tasks.map((task) => { + if (task.id === id) { + return { ...task, completed: !task.completed }; + } + return task; + }); + updateTasksList(updatedTasks); }; + + return ( -
    +
    setNewTask(e.target.value)} - className="flex-grow border rounded-l py-2 px-3" + className="flex-grow rounded-l py-2 px-3 bg-gray-800" />
    -
    - - -
    + */}
      {filteredTasks.map((task) => ( { + let returnObj: Task[] = []; + const storageObj = localStorage.getItem('tasks') || ''; + if (storageObj) returnObj = JSON.parse(storageObj); + return returnObj; +} + +export const setOnLocalStorage = (taskList: Task[]) => { + localStorage.setItem('tasks', JSON.stringify(taskList)); +} \ No newline at end of file From 8168120d42d1eaecfd358561a49b66cc1acba783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20M=C3=BCller?= Date: Tue, 4 Mar 2025 12:03:18 -0300 Subject: [PATCH 3/5] Change some visual, colors and structure. Add logotipe and responsive --- src/App.tsx | 7 +-- src/components/Menu.tsx | 5 +- src/components/MenuItem..tsx | 4 +- src/components/TaskItem.tsx | 4 +- src/components/TaskManager.tsx | 84 ++++++++++++++------------------- src/static/img/logo-fit.png | Bin 0 -> 28558 bytes tailwind.config.js | 21 ++++++++- 7 files changed, 67 insertions(+), 58 deletions(-) create mode 100644 src/static/img/logo-fit.png diff --git a/src/App.tsx b/src/App.tsx index 7377da4..f01ab49 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,9 +4,10 @@ import TaskManager from "./components/TaskManager"; function App() { return ( -
      -
      -

      Task Manager

      +
      +
      + +

      Task Manager

      diff --git a/src/components/Menu.tsx b/src/components/Menu.tsx index 239f9a9..7409d2c 100644 --- a/src/components/Menu.tsx +++ b/src/components/Menu.tsx @@ -1,7 +1,7 @@ import React from "react"; import MenuItem from "./MenuItem."; -const Menu = ({ setFilter }: any) => { +const Menu = ({ setFilter, setFilterName }: any) => { return ( diff --git a/src/components/MenuItem..tsx b/src/components/MenuItem..tsx index b8feba8..08c562a 100644 --- a/src/components/MenuItem..tsx +++ b/src/components/MenuItem..tsx @@ -1,9 +1,9 @@ import React from "react"; -const MenuItem = ({ value, textValue, setFilter }: any) => { +const MenuItem = ({ value, textValue, setFilter, setFilterName }: any) => { return ( -
    • setFilter(value)} id={value + "Button"}>{textValue}
    • +
    • {setFilter(value); setFilterName(textValue)}} id={value + "Button"}>{textValue}
    • ); }; diff --git a/src/components/TaskItem.tsx b/src/components/TaskItem.tsx index a6f6b94..3446981 100644 --- a/src/components/TaskItem.tsx +++ b/src/components/TaskItem.tsx @@ -2,11 +2,11 @@ import React from "react"; const TaskItem = ({ task, onDelete, onToggle }: any) => { return ( -
    • +
    • onToggle(task.id)} className={`cursor-pointer ${ - task.completed ? "line-through text-green-500" : "text-black" + task.completed ? "line-through text-green-500" : "text-white" }`} > {task.title} diff --git a/src/components/TaskManager.tsx b/src/components/TaskManager.tsx index 2641f2e..622d392 100644 --- a/src/components/TaskManager.tsx +++ b/src/components/TaskManager.tsx @@ -9,6 +9,7 @@ const TaskManager = () => { const [tasks, setTasks] = useState([]); const [filter, setFilter] = useState("all"); const [newTask, setNewTask] = useState(""); + const [filterName, setFilterName] = useState("All"); // Get tasks from the local storage on the page mount useEffect(() => { @@ -28,7 +29,7 @@ const TaskManager = () => { else f.classList.remove('border-b-2', 'border-b-white', 'font-bold'); }); }; - + const filteredTasks = tasks.filter((task) => { setSelectedFilters(); if (filter === "completed") return task.completed === true; @@ -67,54 +68,39 @@ const TaskManager = () => { return ( -
      -
      - setNewTask(e.target.value)} - className="flex-grow rounded-l py-2 px-3 bg-gray-800" - /> - -
      - - - {/* - - */} -
        - {filteredTasks.map((task) => ( - - ))} -
      +
      +
      +

      Insert new tasks on the list:

      +
      + setNewTask(e.target.value)} + className="flex-grow rounded-l py-2 px-3 bg-dark-gray-900 text-white" + /> + +
      +
      +
      + +
        +

        {filterName + ' tasks'}!

        + {filteredTasks.map((task) => ( + + ))} +
      +
      ); }; diff --git a/src/static/img/logo-fit.png b/src/static/img/logo-fit.png new file mode 100644 index 0000000000000000000000000000000000000000..417fdec319927f2a6c0c5c2460fb8ea517ba5364 GIT binary patch literal 28558 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 SO7l Date: Tue, 4 Mar 2025 13:22:32 -0300 Subject: [PATCH 4/5] Change fields style and add checkbox to set completed. Add useEffect to control filter and change the screen --- src/App.tsx | 1 - src/components/Menu.tsx | 4 ++-- src/components/TaskItem.tsx | 39 +++++++++++++++++++++------------- src/components/TaskManager.tsx | 30 ++++++++++++++++++++------ tailwind.config.js | 6 ++++++ 5 files changed, 56 insertions(+), 24 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index f01ab49..51c7040 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,7 +9,6 @@ function App() {

      Task Manager

    • -
      ); diff --git a/src/components/Menu.tsx b/src/components/Menu.tsx index 7409d2c..0e99e0b 100644 --- a/src/components/Menu.tsx +++ b/src/components/Menu.tsx @@ -10,13 +10,13 @@ const Menu = ({ setFilter, setFilterName }: any) => { textValue={'All'} setFilter={setFilter} setFilterName={setFilterName} - /> + /> + /> { return ( -
    • - onToggle(task.id)} - className={`cursor-pointer ${ - task.completed ? "line-through text-green-500" : "text-white" - }`} - > - {task.title} - +
    • +
    • ); }; diff --git a/src/components/TaskManager.tsx b/src/components/TaskManager.tsx index 622d392..e460a04 100644 --- a/src/components/TaskManager.tsx +++ b/src/components/TaskManager.tsx @@ -13,9 +13,14 @@ const TaskManager = () => { // Get tasks from the local storage on the page mount useEffect(() => { + setSelectedFilters(); setTasks(getFromLocalStorage()); }, []); + useEffect(() => { + setSelectedFilters(); + }, [filter]) + // Update the state list (not have to go to the localstorage every time to get data) and the localStorage. const updateTasksList = (newList: Task[]) => { setTasks(newList); @@ -31,7 +36,6 @@ const TaskManager = () => { }; const filteredTasks = tasks.filter((task) => { - setSelectedFilters(); if (filter === "completed") return task.completed === true; if (filter === "pending") return task.completed === false; return true; @@ -69,7 +73,7 @@ const TaskManager = () => { return (
      -
      +

      Insert new tasks on the list:

      { -
        -

        {filterName + ' tasks'}!

        - {filteredTasks.map((task) => ( +
          +

          {filterName + ' tasks'}!

          + {filteredTasks.length > 0 ? + filteredTasks.map((task) => ( - ))} + )) + : +
          + {"There are no " + (filter === 'all' ? '' : (filterName + ' ')) + 'tasks yet'} + { + filter === 'completed' ? + "Complete a task to see on this list!" + : + "Add a new one and start to control your tasks!" + } + +
          + }
      diff --git a/tailwind.config.js b/tailwind.config.js index c594a1c..eadcbe0 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -14,12 +14,18 @@ module.exports = { 800: '#2E2E2E', 900: '#1A1A1A', }, + 'cyan-blue': { + 600: '#3F4352', + } }, spacing: { '4px': '4px', '8': '2rem', '16': '4rem', 'xss': '5rem', + }, + minHeight: { + '128': '32rem', // cria uma classe min-h-128 com altura mínima de 32rem } }, }, From f0b3f079191d4d64b995858c38ab2068bc60733f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20M=C3=BCller?= Date: Tue, 4 Mar 2025 16:57:19 -0300 Subject: [PATCH 5/5] Create default button class and confirmation modal, sort list when updated (completed on the end), correct some css --- src/components/Button.tsx | 36 +++++++++++++++ src/components/ConfirmationModal.tsx | 34 ++++++++++++++ src/components/TaskItem.tsx | 19 ++++---- src/components/TaskManager.tsx | 68 ++++++++++++++++++++-------- tailwind.config.js | 4 +- 5 files changed, 133 insertions(+), 28 deletions(-) create mode 100644 src/components/Button.tsx create mode 100644 src/components/ConfirmationModal.tsx diff --git a/src/components/Button.tsx b/src/components/Button.tsx new file mode 100644 index 0000000..0bb49b1 --- /dev/null +++ b/src/components/Button.tsx @@ -0,0 +1,36 @@ +import React from "react"; + +const Button = ({ type, action, params, value, customClass}: any) => { + let mainColor = ''; + let textColor = ''; + let hoverColor = ''; + + switch(type) { + case 'submit': + mainColor = 'blue-500'; + textColor = 'white'; + hoverColor = 'blue-600'; + break; + case 'cancel': + mainColor = 'gray-300'; + textColor = 'gray-700'; + hoverColor = 'gray-400'; + break; + case 'delete': + mainColor = 'red-500'; + textColor = 'white'; + hoverColor = 'red-600'; + break; + } + + return ( + + ) +}; + +export default Button; \ No newline at end of file diff --git a/src/components/ConfirmationModal.tsx b/src/components/ConfirmationModal.tsx new file mode 100644 index 0000000..1d82fe3 --- /dev/null +++ b/src/components/ConfirmationModal.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import Button from "./Button"; + +const ConfirmationModal = ({ taskID, taskTitle, onClose, onConfirm }: any) => { + return ( +
      +
      +

      Confirmation

      +

      { + "Are you sure you want to delete the task '" + taskTitle + "' ?" + } +

      + +
      +
      +
      +
      + ) +}; + +export default ConfirmationModal; \ No newline at end of file diff --git a/src/components/TaskItem.tsx b/src/components/TaskItem.tsx index 7f2139b..a9d347d 100644 --- a/src/components/TaskItem.tsx +++ b/src/components/TaskItem.tsx @@ -1,4 +1,5 @@ import React from "react"; +import Button from "./Button"; const TaskItem = ({ task, onDelete, onToggle }: any) => { return ( @@ -10,22 +11,24 @@ const TaskItem = ({ task, onDelete, onToggle }: any) => { checked={task.completed} onChange={() => onToggle(task.id)} > -
      +
      {task.title}
      - + + +
      + setNewTask(e.target.value)} + className="flex-grow rounded-l py-4 px-3 bg-dark-gray-900 text-white rounded-l" + /> +
    @@ -101,7 +125,7 @@ const TaskManager = () => { )) @@ -119,6 +143,14 @@ const TaskManager = () => { }
    + {isModalOpen && + + }
    ); }; diff --git a/tailwind.config.js b/tailwind.config.js index eadcbe0..e26fec1 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -16,7 +16,7 @@ module.exports = { }, 'cyan-blue': { 600: '#3F4352', - } + }, }, spacing: { '4px': '4px', @@ -26,7 +26,7 @@ module.exports = { }, minHeight: { '128': '32rem', // cria uma classe min-h-128 com altura mínima de 32rem - } + }, }, }, plugins: [],