diff --git a/src/App.tsx b/src/App.tsx index fcd2850..51c7040 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,11 +4,11 @@ import TaskManager from "./components/TaskManager"; function App() { return ( -
-
-

Task Manager

+
+
+ +

Task Manager

-
); 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/Menu.tsx b/src/components/Menu.tsx new file mode 100644 index 0000000..0e99e0b --- /dev/null +++ b/src/components/Menu.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import MenuItem from "./MenuItem."; + +const Menu = ({ setFilter, setFilterName }: 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..08c562a --- /dev/null +++ b/src/components/MenuItem..tsx @@ -0,0 +1,10 @@ +import React from "react"; + + +const MenuItem = ({ value, textValue, setFilter, setFilterName }: any) => { + return ( +
  • {setFilter(value); setFilterName(textValue)}} 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..a9d347d 100644 --- a/src/components/TaskItem.tsx +++ b/src/components/TaskItem.tsx @@ -1,28 +1,35 @@ import React from "react"; +import Button from "./Button"; const TaskItem = ({ task, onDelete, onToggle }: any) => { return ( -
  • - onToggle(task.id)} - className={`cursor-pointer ${ - task.isCompleted ? "text-black" : "line-through text-green-500" - }`} - > - {task.title} - +
  • +
  • ); }; diff --git a/src/components/TaskManager.tsx b/src/components/TaskManager.tsx index 7b280e8..e5d01b5 100644 --- a/src/components/TaskManager.tsx +++ b/src/components/TaskManager.tsx @@ -1,87 +1,156 @@ -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 ConfirmationModal from "./ConfirmationModal"; +import Button from "./Button"; +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(); + const [newTask, setNewTask] = useState(""); + const [filterName, setFilterName] = useState("All"); + const [isModalOpen, setIsModalOpen] = useState(false); + const [selectedTask, setSelectedTask] = useState(); - // Intentional bug: The filter conditions are reversed. + // Get tasks from the local storage on the page mount + useEffect(() => { + 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); + 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) => { - 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; }); 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 confirmDeleteTask = (task: Task) => { + setSelectedTask(task) + setIsModalOpen(true); + } + + const closeModal = () => { + setIsModalOpen(false); + } + 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); + closeModal(); }; 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; + }); + //Sort the list to put the completed on the end. The secondary sort is by id. + updateTasksList(updatedTasks.sort((a, b) => { + if (a.completed === b.completed) { + return a.id - b.id; + } + return a.completed - b.completed; + })); + }; return ( -
    -
    - setNewTask(e.target.value)} - className="flex-grow border rounded-l py-2 px-3" - /> - -
    -
    - - - +
    + {/* Using this hidden class to instanciate color because of a major problem on Tailwind where the color's variable are not iniciated before use */} +
    +
    +

    Insert new tasks on the list:

    +
    +
    + setNewTask(e.target.value)} + className="flex-grow rounded-l py-4 px-3 bg-dark-gray-900 text-white rounded-l" + /> +
    +
    -
      - {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!" + } + +
        + } +
      +
    + {isModalOpen && + - ))} - + }
    ); }; diff --git a/src/interfaces/task.interface.ts b/src/interfaces/task.interface.ts new file mode 100644 index 0000000..05d2410 --- /dev/null +++ b/src/interfaces/task.interface.ts @@ -0,0 +1,7 @@ +interface Task { + id: number, + title: string, + completed: boolean, +} + +export default Task; \ No newline at end of file diff --git a/src/static/img/logo-fit.png b/src/static/img/logo-fit.png new file mode 100644 index 0000000..417fdec Binary files /dev/null and b/src/static/img/logo-fit.png differ diff --git a/src/store/task.ts b/src/store/task.ts new file mode 100644 index 0000000..5793979 --- /dev/null +++ b/src/store/task.ts @@ -0,0 +1,12 @@ +import Task from "../interfaces/task.interface"; + +export const getFromLocalStorage = () : 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 diff --git a/tailwind.config.js b/tailwind.config.js index ffb9a83..e26fec1 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -2,7 +2,32 @@ module.exports = { content: ["./index.html", "./src/**/*.{ts,tsx}"], theme: { - extend: {}, + extend: { + colors: { + 'light-blue': { + 800: '#0277bd', + 900: '#01579b', + }, + 'dark-gray': { + 600: '#575757', + 700: '#333333', + 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 + }, + }, }, plugins: [], };