Skip to content

Commit

Permalink
TodoApp v.2
Browse files Browse the repository at this point in the history
  • Loading branch information
ZupaFly committed Sep 24, 2024
1 parent 8ea49d4 commit c47ade5
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 90 deletions.
23 changes: 12 additions & 11 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,29 @@ import { Footer } from './components/Footer';
import { DispatchContext, StateContext } from './Storage/storageFiles';
import { Todo } from './types/Todo';

const getTodosFromLocalStorage: Todo[] = JSON.parse(
localStorage.getItem('todos') || '[]',
);

export const App: React.FC = () => {
const { todos } = useContext(StateContext);
const dispatch = useContext(DispatchContext);

useEffect(() => {
if (todos.length === 0) {
dispatch({ type: 'setTodos', tod: getTodosFromLocalStorage });
}
}, []);
const temper = useRef(true);

useEffect(() => {
if (temper.current) {
temper.current = false;

return;
try {
const storedTodos: Todo[] = JSON.parse(
localStorage.getItem('todos') || '[]',
);

dispatch({ type: 'setTodos', tod: storedTodos });
} catch (error) {
// eslint-disable-next-line no-console
console.error('Failed to load todos from localStorage:', error);
}
}
}, [dispatch]);

useEffect(() => {
localStorage.setItem('todos', JSON.stringify(todos));
}, [todos]);

Expand Down
7 changes: 6 additions & 1 deletion src/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const Footer = () => {
{todos.length !== 0 && (
<footer className="todoapp__footer" data-cy="Footer">
<span className="todo-count" data-cy="TodosCounter">
{todosLeft} items left
{`${todosLeft} ${todosLeft === 1 ? 'item left' : 'items left'}`}
</span>

<nav className="filter" data-cy="Filter">
Expand Down Expand Up @@ -56,6 +56,11 @@ export const Footer = () => {
data-cy="ClearCompletedButton"
onClick={() => dispatch({ type: 'clearAll' })}
disabled={todosLeft === todos.length}
style={{
visibility: todos.some(todo => todo.completed)
? 'visible'
: 'hidden',
}}
>
Clear completed
</button>
Expand Down
16 changes: 8 additions & 8 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useContext, useRef } from 'react';
import React, { useContext, useEffect, useRef } from 'react';
import { DispatchContext, StateContext } from '../Storage/storageFiles';
import cn from 'classnames';

Expand All @@ -24,11 +24,13 @@ export const Header = () => {

const inputRef = useRef<HTMLInputElement>(null);

if (focusNewTodo) {
inputRef.current?.focus();
} else {
inputRef.current?.blur();
}
useEffect(() => {
if (focusNewTodo) {
inputRef.current?.focus();
} else {
inputRef.current?.blur();
}
}, [focusNewTodo]);

return (
<header className="todoapp__header">
Expand All @@ -53,8 +55,6 @@ export const Header = () => {
className="todoapp__new-todo"
placeholder="What needs to be done?"
value={newTodo}
onClick={() => dispatch({ type: 'setFocudNewTodo' })}
onBlur={() => dispatch({ type: 'setFocudNewTodo' })}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
dispatch({
type: 'changeTodo',
Expand Down
87 changes: 87 additions & 0 deletions src/components/Todo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React, { useContext } from 'react';
import { DispatchContext } from '../Storage/storageFiles';
import cn from 'classnames';

interface TodoItemProps {
todo: {
id: number;
title: string;
completed: boolean;
changed: boolean;
};
handleKeyUp: (
e: React.KeyboardEvent<HTMLInputElement>,
todoId: number,
) => void;
handleFormSubmit: (
e: React.FormEvent<HTMLFormElement>,
todoId: number,
) => void;
}

export const TodoItem: React.FC<TodoItemProps> = ({
todo,
handleKeyUp,
handleFormSubmit,
}) => {
const dispatch = useContext(DispatchContext);

return (
<div
data-cy="Todo"
className={cn('todo', { completed: todo.completed })}
key={todo.id}
>
<label className="todo__status-label">
<input
data-cy="TodoStatus"
type="checkbox"
className="todo__status"
checked={todo.completed}
onChange={() => dispatch({ type: 'checked', id: todo.id })}
/>
</label>

<span
data-cy="TodoTitle"
className="todo__title"
onDoubleClick={() => dispatch({ type: 'setChanged', id: todo.id })}
>
{todo.changed ? null : todo.title}
</span>

{todo.changed ? (
<form onSubmit={e => handleFormSubmit(e, todo.id)}>
<input
data-cy="TodoTitleField"
type="text"
className="todo__title-field"
placeholder="Empty todo will be deleted"
value={todo.title}
onKeyUp={e => handleKeyUp(e, todo.id)}
autoFocus
onChange={e =>
dispatch({
type: 'changed',
id: todo.id,
text: e.target.value,
})
}
onBlur={() => dispatch({ type: 'setChanged', id: todo.id })}
/>
</form>
) : null}

{!todo.changed ? (
<button
type="button"
className="todo__remove"
data-cy="TodoDelete"
onClick={() => dispatch({ type: 'remove', id: todo.id })}
>
×
</button>
) : null}
</div>
);
};
94 changes: 24 additions & 70 deletions src/components/Todos.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
import { useContext } from 'react';
import { DispatchContext, StateContext } from '../Storage/storageFiles';
import cn from 'classnames';
import { TodoItem } from './Todo';

export const Todos = () => {
const dispatch = useContext(DispatchContext);
const { todos, useTodos } = useContext(StateContext);

const handleFormSubmit = (
e: React.FormEvent<HTMLFormElement>,
todoId: number,
) => {
e.preventDefault();
dispatch({ type: 'setChanged', id: todoId });
};

const handleKeyUp = (
e: React.KeyboardEvent<HTMLInputElement>,
todoId: number,
) => {
if (e.key === 'Escape') {
dispatch({ type: 'escapeChangedText', id: todoId });
dispatch({ type: 'setChanged', id: todoId });
}
};

const todosFilter = todos.filter(todo => {
if (useTodos === 'Active') {
return !todo.completed;
Expand All @@ -21,76 +39,12 @@ export const Todos = () => {
return (
<section className="todoapp__main" data-cy="TodoList">
{todosFilter.map(todo => (
<div
data-cy="Todo"
className={cn('todo', { completed: todo.completed })}
<TodoItem
key={todo.id}
>
<label className="todo__status-label">
<input
data-cy="TodoStatus"
type="checkbox"
className="todo__status"
checked={todo.completed}
onChange={() => dispatch({ type: 'checked', id: todo.id })}
/>
</label>

{!todo.changed && (
<span
data-cy="TodoTitle"
className="todo__title"
onDoubleClick={() =>
dispatch({ type: 'setChanged', id: todo.id })
}
>
{todo.title}
</span>
)}

{todo.changed && (
<form
onSubmit={e => {
e.preventDefault();
dispatch({ type: 'setChanged', id: todo.id });
}}
>
<input
data-cy="TodoTitleField"
type="text"
className="todo__title-field"
placeholder="Empty todo will be deleted"
value={todo.title}
onKeyUp={e => {
if (e.key === 'Escape') {
dispatch({ type: 'escapeChangedText', id: todo.id });
dispatch({ type: 'setChanged', id: todo.id });
}
}}
autoFocus
onChange={e =>
dispatch({
type: 'changed',
id: todo.id,
text: e.target.value,
})
}
onBlur={() => dispatch({ type: 'setChanged', id: todo.id })}
/>
</form>
)}

{!todo.changed && (
<button
type="button"
className="todo__remove"
data-cy="TodoDelete"
onClick={() => dispatch({ type: 'remove', id: todo.id })}
>
×
</button>
)}
</div>
todo={todo}
handleKeyUp={handleKeyUp}
handleFormSubmit={handleFormSubmit}
/>
))}
</section>
);
Expand Down

0 comments on commit c47ade5

Please sign in to comment.