From 4dd297a63e21ebaf36f53e95d37ff9508e8e6b11 Mon Sep 17 00:00:00 2001 From: Oksana Shvets Date: Wed, 22 Nov 2023 10:42:55 +0100 Subject: [PATCH] Solution for fix2 --- src/components/AddTodo.tsx | 67 ++++++++++++++++++++++++++ src/components/TodoItem.tsx | 93 +++++++++++++++++++++++++++++++++++++ src/components/TodoList.tsx | 20 ++++++++ src/context/TodoContext.tsx | 30 ++++++++++++ src/types/Todo.ts | 5 ++ 5 files changed, 215 insertions(+) create mode 100644 src/components/AddTodo.tsx create mode 100644 src/components/TodoItem.tsx create mode 100644 src/components/TodoList.tsx create mode 100644 src/context/TodoContext.tsx create mode 100644 src/types/Todo.ts diff --git a/src/components/AddTodo.tsx b/src/components/AddTodo.tsx new file mode 100644 index 000000000..f4da9a872 --- /dev/null +++ b/src/components/AddTodo.tsx @@ -0,0 +1,67 @@ +/* eslint-disable no-console */ +import React, { + useState, useEffect, ChangeEvent, useContext, +} from 'react'; +import { TodoContext } from '../context/TodoContext'; + +export const AddTodo: React.FC = () => { + const [title, setTitle] = useState(''); + const { todos, setTodos } = useContext(TodoContext); + const [, setErrorMessage] = useState(null); + + const handleAddTodo = (e: React.KeyboardEvent) => { + console.log('start'); + if (e.key === 'Enter') { + e.preventDefault(); + } + + console.log('next'); + console.log(title); + + if (title.trim() === '') { + setErrorMessage('Field can not be blank'); + console.log('emty'); + } else { + console.log('else'); + + setTodos((prevTodos) => [ + ...prevTodos, + { id: new Date().getTime(), title, completed: false }, + ]); + setTitle(''); + setErrorMessage(null); + } + }; + + useEffect(() => { + console.log('Todos updated:', todos); + + localStorage.setItem('todos', JSON.stringify(todos)); + }, [todos]); + + const handleInputChange = (e: ChangeEvent) => { + setTitle(e.target.value); + }; + + const handleKeyPress = (event: React.KeyboardEvent) => { + console.log('Key pressed:', event.key); + + if (event.key === 'Enter') { + handleAddTodo(event); + } + }; + + return ( +
+ +
+ ); +}; diff --git a/src/components/TodoItem.tsx b/src/components/TodoItem.tsx new file mode 100644 index 000000000..7258138c2 --- /dev/null +++ b/src/components/TodoItem.tsx @@ -0,0 +1,93 @@ +import React, { + useContext, useState, useEffect, useRef, ChangeEvent, +} from 'react'; +import { TodoContext } from '../context/TodoContext'; +import { Todo } from '../types/Todo'; + +type TodoItemProps = { + todo: Todo; +}; + +export const TodoItem: React.FC = ({ todo }) => { + const { todos, setTodos } = useContext(TodoContext); + const [isEditing, setEditing] = useState(false); + const [editedTitle, setEditedTitle] = useState(todo.title); + const inputRef = useRef(null); + + const completeTodo = (e: ChangeEvent) => { + const updatedTodos = todos.map((item: Todo) => (item.id === todo.id + ? { ...item, completed: e.target.checked } : item)); + + setTodos(updatedTodos); + }; + + const deleteTodo = () => { + const updatedTodos = todos.filter((item) => item.id !== todo.id); + + setTodos(updatedTodos); + }; + + const handleDoubleClick = () => { + setEditing(true); + }; + + const handleBlur = () => { + if (editedTitle.trim() === '') { + deleteTodo(); + } else { + const updatedTodos = todos.map((item) => (item.id === todo.id + ? { ...item, title: editedTitle.trim() } : item)); + + setTodos(updatedTodos); + } + + setEditing(false); + }; + + const handleKeyUp = (e: React.KeyboardEvent) => { + if (e.key === 'Escape') { + setEditedTitle(todo.title); + setEditing(false); + } else if (e.key === 'Enter') { + handleBlur(); + } + }; + + useEffect(() => { + if (isEditing && inputRef.current) { + inputRef.current.focus(); + } + }, [isEditing]); + + return ( +
  • +
    + + +
    + {isEditing && ( + setEditedTitle(e.target.value)} + onBlur={handleBlur} + onKeyUp={handleKeyUp} + /> + )} +
  • + ); +}; diff --git a/src/components/TodoList.tsx b/src/components/TodoList.tsx new file mode 100644 index 000000000..e5f832e3b --- /dev/null +++ b/src/components/TodoList.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { TodoItem } from './TodoItem'; +import { Todo } from '../types/Todo'; + +type Props = { + todos: Todo[]; +}; + +export const TodoList: React.FC = ({ todos }) => { + // eslint-disable-next-line no-console + console.log('Todos in TodoList:', todos); + + return ( +
      + {todos.map((todo) => ( + + ))} +
    + ); +}; diff --git a/src/context/TodoContext.tsx b/src/context/TodoContext.tsx new file mode 100644 index 000000000..0a45e0fcc --- /dev/null +++ b/src/context/TodoContext.tsx @@ -0,0 +1,30 @@ +import React, { useState, ReactNode, useMemo } from 'react'; +import { Todo } from '../types/Todo'; + +const getTodos = JSON.parse(localStorage.getItem('todos') || '[]') as Todo[]; + +export const TodoContext = React.createContext<{ + todos: Todo[]; + setTodos: React.Dispatch>; +}>({ + todos: getTodos, + setTodos: () => {}, +}); + +interface TodoProviderProps { + children: ReactNode; +} + +export const TodoProvider: React.FC = ({ children }) => { + const [todos, setTodos] = useState(getTodos); + const value = useMemo(() => ({ + todos, + setTodos, + }), [todos]); + + return ( + + {children} + + ); +}; diff --git a/src/types/Todo.ts b/src/types/Todo.ts new file mode 100644 index 000000000..d94ea1bff --- /dev/null +++ b/src/types/Todo.ts @@ -0,0 +1,5 @@ +export type Todo = { + id: number; + title: string; + completed: boolean; +};