diff --git a/src/App.tsx b/src/App.tsx index 658bbfc30..487857e25 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,11 +1,224 @@ -import React from 'react'; -import { TodosProvider } from './Utils/TodosContext'; -import { TodoApp } from './Components/TodoApp/TodoApp'; +import React, { useState, useMemo } from 'react'; +import { Todo } from './types/Todo'; +import { TodoList } from './сomponents/TodoList/TodoList'; +import { TodosFilter } from './сomponents/TodoFilter/TodoFilter'; + +enum Status { + all = '#/', + active = '#/active', + completed = '#/completed', +} + +function filterTodos(todos: Todo[], status: string):Todo[] { + return todos.filter(todo => { + if (status === Status.active) { + return !todo.completed; + } + + if (status === Status.completed) { + return todo.completed; + } + + return true; + }); +} + +const storedTodos = localStorage.getItem('todos'); +const todos = storedTodos ? JSON.parse(storedTodos) : []; export const App: React.FC = () => { + const [todo, setTodo] = useState(''); + const [todoList, setTodoList] = useState(todos); + const [statusButton, setStatusButton] = useState('#/'); + const [isEdited, setIsEdited] = useState(''); + + const visibleTodos = useMemo(() => filterTodos(todoList, statusButton), + [todoList, statusButton]); + + const changeNameAction + = ( + event: React.KeyboardEvent, + currTodo: Todo, + currValue: string, + ) => { + if (event.key === 'Enter') { + if (currValue.trim() !== '') { + const changeable = [...todoList] + .map(changeableTodo => { + if (currTodo.id === changeableTodo.id) { + return { + ...changeableTodo, + title: currValue, + }; + } + + return changeableTodo; + }); + + setTodoList(changeable); + setIsEdited(''); + } + + if (currValue.trim() === '') { + const changeable = [...todoList] + .filter(changeableTodo => currTodo.id !== changeableTodo.id); + + setTodoList(changeable); + setIsEdited(''); + } + } + + if (event.key === 'Escape') { + setIsEdited(''); + } + }; + + const handleSetTodo = (event: React.ChangeEvent) => { + setTodo(event.target.value); + }; + + const todoLeftCount = () => { + let count = 0; + + todoList.forEach(currentTodo => { + if (!currentTodo.completed) { + count += 1; + } + }); + + return count; + }; + + const addTodo = (event: React.FormEvent) => { + event.preventDefault(); + + const newTodo: Todo = { + id: +new Date(), + title: todo, + completed: false, + }; + + setTodoList((prevTodoList) => [...prevTodoList, newTodo]); + setTodo(''); + }; + + const handleDeleteTodo = (todoId: number) => { + setTodoList(currentTodoList => { + return currentTodoList.filter(todoItem => todoId !== todoItem.id); + }); + }; + + const handleChangeCheckbox = (todoId: number) => { + setTodoList(currentTodoList => { + return currentTodoList + .map(todoItem => { + return (todoId === todoItem.id) + ? { + ...todoItem, + completed: !todoItem.completed, + } + : todoItem; + }); + }); + }; + + const handleClearCompleted = () => { + setTodoList(currentTodoList => { + return currentTodoList.filter(currTodo => !currTodo.completed); + }); + }; + + const statusChange = (newStatus: string) => { + setStatusButton(newStatus); + }; + + const isAnyCompleted = todoList.some(currTodo => currTodo.completed === true); + + const toggleAllAction + = () => { + const isAllCompleted = todoList + .every(currTodo => currTodo.completed === true); + + if (isAllCompleted) { + setTodoList(todoList.map(currentTodo => ({ + ...currentTodo, + completed: false, + }))); + } + + if (!isAllCompleted) { + setTodoList(todoList.map(currentTodo => ({ + ...currentTodo, + completed: true, + }))); + } + }; + + const handleChangeTodoTitle = (event: string) => { + setIsEdited(event); + }; + return ( - - - +
+
+

todos

+ +
+ +
+
+
+ + + + {todoList.length > 0 && ( + + )} +
+ + {todoList.length > 0 && ( +
+ + {`${todoLeftCount()} items left`} + + + + {isAnyCompleted && ( + + )} +
+ )} +
); }; diff --git a/src/Components/TodoApp/TodoApp.tsx b/src/Components/TodoApp/TodoApp.tsx deleted file mode 100644 index 3003e45dd..000000000 --- a/src/Components/TodoApp/TodoApp.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import React, { useContext, useState } from 'react'; - -import { TodosContext } from '../../Utils/TodosContext'; -import { TodoList } from '../TodoList/TodoList'; -import { TodosFilter } from '../TodoFilter/TodoFilter'; -import { Filter } from '../../Utils/Types/Filter'; - -type Props = { -}; - -export const TodoApp: React.FC = () => { - const { todos, setTodos } = useContext(TodosContext); - const [title, setTitle] = useState(''); - const [filter, setFilter] = useState(Filter.ALL); - - const handlerFilterChange = (newFilter: string) => { - setFilter(newFilter); - }; - - const handlerChangeTitle = (event: React.ChangeEvent) => { - setTitle(event.target.value); - }; - - const handlerAddTodo = (event: React.FormEvent) => { - event.preventDefault(); - if (title.trim() !== '') { - const newTask = { - id: +new Date(), - title, - completed: false, - }; - - setTodos((prevTodos) => [...prevTodos, newTask]); - setTitle(''); - } - }; - - const handlerCompleteAll = () => { - if (todos.some(todo => todo.completed === false)) { - const updatedTodos = todos.map((todo) => ({ - ...todo, - completed: true, - })); - - setTodos(updatedTodos); - } else { - const updatedTodos = todos.map((todo) => ({ - ...todo, - completed: false, - })); - - setTodos(updatedTodos); - } - }; - - const handlerClearCompletes = () => { - setTodos(currentTodos => currentTodos - .filter(elem => !elem.completed)); - }; - - const filteredTodos = todos.filter((todo) => { - if (filter === Filter.ALL) { - return true; - } - - if (filter === Filter.ACTIVE) { - return !todo.completed; - } - - return todo.completed; - }); - - const noCompleteTodos = todos.filter(elem => !elem.completed); - const isSomeComplete = todos.some(todo => todo.completed === true); - const allCompleted = todos.every(todo => todo.completed === true); - - return ( -
-
-

todos

- -
- -
-
- - {todos.length !== 0 && ( -
- - - {todos.length !== 0 && ( - - )} - - - -
- )} - - {todos.length !== 0 && ( -
- - {`${noCompleteTodos.length} items left`} - - - - - {isSomeComplete && ( - - )} - -
- )} -
- ); -}; diff --git a/src/Components/TodoFilter/TodoFilter.tsx b/src/Components/TodoFilter/TodoFilter.tsx deleted file mode 100644 index 1f3fd994c..000000000 --- a/src/Components/TodoFilter/TodoFilter.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import classNames from 'classnames'; -import { Filter } from '../../Utils/Types/Filter'; - -type Props = { - currentFilter: string, - onFilterChange?: (filter: string) => void -}; - -export const TodosFilter: React.FC = ({ - currentFilter, - onFilterChange = () => { }, -}) => { - return ( - - ); -}; diff --git a/src/Components/TodoItem/TodoItem.tsx b/src/Components/TodoItem/TodoItem.tsx deleted file mode 100644 index 4392bf414..000000000 --- a/src/Components/TodoItem/TodoItem.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import React, { useContext, useRef, useState } from 'react'; -import classNames from 'classnames'; - -import { Todo } from '../../Utils/Types/Todo'; -import { TodosContext } from '../../Utils/TodosContext'; - -type Props = { - todo: Todo, -}; - -export const TodoItem: React.FC = ({ todo }) => { - const { todos, setTodos } = useContext(TodosContext); - const [newTitle, setNewTitle] = useState(''); - const [isEditing, setIsEditing] = useState(false); - - const titleField = useRef(null); - - const handlerDeleteTodo = () => { - setTodos(currentTodos => currentTodos - .filter(elem => elem.id !== todo.id)); - }; - - const handlerCompleteTodo = () => { - const updatedTodos = [...todos]; - const currentTodoIndex = updatedTodos - .findIndex((elem: Todo) => elem.id === todo.id); - - if (currentTodoIndex !== -1) { - const newCompleted = !updatedTodos[currentTodoIndex].completed; - - updatedTodos[currentTodoIndex] = { - ...updatedTodos[currentTodoIndex], - completed: newCompleted, - }; - updatedTodos.splice(currentTodoIndex, 1, updatedTodos[currentTodoIndex]); - - setTodos(updatedTodos); - } - }; - - const handlerEditTitle = (event: React.ChangeEvent) => { - setNewTitle(event.target.value); - }; - - const handlerEditTodo = () => { - setNewTitle(todo.title); - setIsEditing(true); - - setTimeout(() => { - if (titleField.current !== null) { - titleField.current.focus(); - } - }, 0); - }; - - const handlerEndEditTodoOnBlur = () => { - if (isEditing) { - if (newTitle !== '') { - const updatedTodos = [...todos]; - const currentTodoIndex = updatedTodos - .findIndex((elem: Todo) => elem.id === todo.id); - - if (currentTodoIndex !== -1) { - updatedTodos[currentTodoIndex] = { - ...updatedTodos[currentTodoIndex], - title: newTitle.trim(), - }; - updatedTodos - .splice(currentTodoIndex, 1, updatedTodos[currentTodoIndex]); - - setTodos(updatedTodos); - } - } else { - setTodos(currentTodos => currentTodos - .filter(elem => elem.id !== todo.id)); - } - } - - setIsEditing(false); - }; - - const handlerKeyUp = (event: React.KeyboardEvent) => { - if (event.key === 'Escape' && isEditing) { - setIsEditing(false); - } else if (event.key === 'Enter' && isEditing) { - handlerEndEditTodoOnBlur(); - } - }; - - return ( -
  • -
    - - -
    - -
  • - ); -}; diff --git a/src/Components/TodoList/TodoList.tsx b/src/Components/TodoList/TodoList.tsx deleted file mode 100644 index eb95f270f..000000000 --- a/src/Components/TodoList/TodoList.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Todo } from '../../Utils/Types/Todo'; -import { TodoItem } from '../TodoItem/TodoItem'; - -type Props = { - todos: Todo[], -}; - -export const TodoList: React.FC = ({ - todos, -}) => { - return ( -
      - {todos.map((todo: Todo) => ( - - ))} -
    - ); -}; diff --git a/src/Utils/TodosContext.tsx b/src/Utils/TodosContext.tsx deleted file mode 100644 index 3c26b94d4..000000000 --- a/src/Utils/TodosContext.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React, { createContext, useEffect, useState } from 'react'; -import { Todo } from './Types/Todo'; - -interface TodosContextProps { - children: React.ReactNode; -} - -export const TodosContext = createContext<{ - todos: Todo[]; - setTodos: React.Dispatch>; -}>({ - todos: [], - setTodos: () => {}, -}); - -export const TodosProvider: React.FC = ({ children }) => { - const [todos, setTodos] = useState([]); - - useEffect(() => { - const storedTodos = localStorage.getItem('todos'); - - if (storedTodos) { - setTodos(JSON.parse(storedTodos)); - } - }, []); - - useEffect(() => { - localStorage.setItem('todos', JSON.stringify(todos)); - }, [todos]); - - return ( - - {children} - - ); -}; diff --git a/src/Utils/Types/Filter.tsx b/src/Utils/Types/Filter.tsx deleted file mode 100644 index 4730e848c..000000000 --- a/src/Utils/Types/Filter.tsx +++ /dev/null @@ -1,5 +0,0 @@ -export const Filter = { - ALL: '', - ACTIVE: 'active', - COMPLETED: 'completed', -}; diff --git a/src/Utils/Types/Todo.tsx b/src/types/Todo.ts similarity index 100% rename from src/Utils/Types/Todo.tsx rename to src/types/Todo.ts diff --git "a/src/\321\201omponents/TodoFilter/TodoFilter.tsx" "b/src/\321\201omponents/TodoFilter/TodoFilter.tsx" new file mode 100644 index 000000000..76efa9949 --- /dev/null +++ "b/src/\321\201omponents/TodoFilter/TodoFilter.tsx" @@ -0,0 +1,50 @@ +import React from 'react'; +import cn from 'classnames'; + +enum Status { + all = '#/', + active = '#/active', + completed = '#/completed', +} + +interface Props { + status: string, + onChangeStatus: (el: string) => void, +} + +export const TodosFilter: React.FC = ({ status, onChangeStatus }) => ( + +); diff --git "a/src/\321\201omponents/TodoList/TodoList.tsx" "b/src/\321\201omponents/TodoList/TodoList.tsx" new file mode 100644 index 000000000..8305ffe98 --- /dev/null +++ "b/src/\321\201omponents/TodoList/TodoList.tsx" @@ -0,0 +1,78 @@ +import React, { useState, useEffect } from 'react'; +import cn from 'classnames'; +import { Todo } from '../../types/Todo'; + +type Props = { + todos: Todo[], + onChange: (el: number) => void, + onDelete: (el: number) => void, + edit: string, + onEdit: (el: string) => void, + onChangeName: ( + el: React.KeyboardEvent, + currTodo: Todo, + currValue: string, + ) => void, +}; + +export const TodoList: React.FC + = ({ + todos, + onChange, + onDelete, + edit, + onEdit, + onChangeName, + }) => { + const [currInputValue, setCurrInputValue] = useState(edit); + + useEffect(() => { + localStorage.setItem('todos', JSON.stringify(todos)); + }, [todos]); + + return ( +
      + {todos.map(todo => ( +
    • { + onEdit(todo.title); + setCurrInputValue(todo.title); + }} + > +
      + onChange(todo.id)} + /> + +
      + setCurrInputValue(event.target.value)} + onKeyUp={event => onChangeName(event, todo, currInputValue)} + onBlur={() => onEdit('')} + /> +
    • + ))} +
    + ); + };