Skip to content

Commit

Permalink
add task solution
Browse files Browse the repository at this point in the history
  • Loading branch information
DimaDamage91 committed Dec 20, 2024
1 parent 5f059b5 commit b37ff65
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 34 deletions.
102 changes: 79 additions & 23 deletions src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const App: React.FC = () => {
const [todos, setTodos] = useState<Todo[]>([]);
const [tempTodo, setTempTodo] = useState<Todo | null>(null);
const [newTodoTitle, setNewTodoTitle] = useState('');
const [selectedTodo, setSelectedTodo] = useState<Todo | null>(null);


const [error, setError] = useState<string | null>(null);
const [isErrorVisible, setIsErrorVisible] = useState(true);
Expand All @@ -25,6 +25,15 @@ export const App: React.FC = () => {
const inputRef = React.useRef<HTMLInputElement>(null);


const handleUpdateTodo = (updatedTodo: Todo) => {
setTodos(prevTodos =>
prevTodos.map(todo =>
todo.id === updatedTodo.id ? { ...todo, title: updatedTodo.title } : todo
)
);
};


const handleError = (errorType: string) => {
switch (errorType) {
case 'load':
Expand Down Expand Up @@ -105,32 +114,33 @@ export const App: React.FC = () => {
});
};

const handleTodoDoubleClick = (todo: Todo) => {
setSelectedTodo(todo);
}

const updateTodo = (updatedTodo: Todo) => {
setError('');

setTodos((currentTodos) =>
currentTodos.map((todo) =>
todo.id === updatedTodo.id ? { ...todo, isLoading: true } : todo
)
);

todoService
.updateTodo(updatedTodo)
.then((todo) => {
setTodos((currentTodos) => {
const newTodos = [...currentTodos];
const index = newTodos.findIndex(todo => todo.id === updatedTodo.id);

if (index !== -1) {
newTodos[index] = todo;
}

return newTodos;
.then((updatedTodoResponse) => {
setTodos((currentTodos) =>
currentTodos.map((todo) =>
todo.id === updatedTodoResponse.id ? updatedTodoResponse : todo
)
);
})
.catch(() => {
setError('Unable to update a todo');
setTodos((currentTodos) =>
currentTodos.map((todo) =>
todo.id === updatedTodo.id ? { ...todo, isLoading: false } : todo
)
);
});
setSelectedTodo(null);
})
.catch(() => {
setError(`Can't update a todo`)
});
}
};

const clearCompletedTodos = () => {
const completedTodos = todos.filter((todo) => todo.completed);
Expand Down Expand Up @@ -180,6 +190,49 @@ export const App: React.FC = () => {
return <UserWarning />;
}

const toggleTodoCompletion = (todoId: number) => {
const targetTodo = todos.find(todo => todo.id === todoId);

if (!targetTodo) return;

const updatedTodo = { ...targetTodo, completed: !targetTodo.completed };

updateTodo(updatedTodo);
};

const toggleAllTodos = () => {
const allCompleted = todos.every(todo => todo.completed);
const updatedStatus = !allCompleted;

const updatedTodos = todos.map(todo =>
todo.completed !== updatedStatus
? { ...todo, completed: updatedStatus }
: todo
);

Promise.allSettled(
updatedTodos.map(todo =>
todo.completed !== updatedStatus ? todoService.updateTodo(todo) : null
)
)
.then(results => {
const successfulIds = results
.map((result, index) =>
result.status === 'fulfilled' ? updatedTodos[index].id : null
)
.filter((id): id is number => id !== null);

setTodos(currentTodos =>
currentTodos.map(todo =>
successfulIds.includes(todo.id)
? { ...todo, completed: updatedStatus }
: todo
)
)
})
.catch(() => handleError('update'));
};

return (
<div className="todoapp">
<h1 className="todoapp__title">todos</h1>
Expand All @@ -191,15 +244,18 @@ export const App: React.FC = () => {
newTodoTitle={newTodoTitle}
setNewTodoTitle={setNewTodoTitle}
inputRef={inputRef}
todos={todos}
onToggleAll={toggleAllTodos}
/>
<TodoList
todos={todos}
tempTodo={tempTodo}
setTodos={setTodos}
filter={filter}
onUpdate={handleUpdateTodo}
onDelete={deleteTodo}
onTodoDoubleClick={handleTodoDoubleClick}
onUpdate={updateTodo}
onToggleCompletion={toggleTodoCompletion}
onToggleAll={toggleAllTodos}
/>
{todos.length > 0 && (
<Footer todos={todos} filter={filter} setFilter={setFilter} clearCompletedTodos={clearCompletedTodos}/>
Expand Down
18 changes: 16 additions & 2 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import classNames from 'classnames';
import React from 'react';
import { Todo } from '../types/Todo';

interface HeaderProps { //+++
onAdd: (title: string) => void;
isAdding: boolean;
newTodoTitle: string;
setNewTodoTitle: React.Dispatch<React.SetStateAction<string>>;
inputRef: React.RefObject<HTMLInputElement>;
todos: Todo[];
onToggleAll: () => void;
}

export const Header: React.FC<HeaderProps> = ({ onAdd, isAdding, newTodoTitle, setNewTodoTitle, inputRef }) => { //+++
export const Header: React.FC<HeaderProps> = ({ onAdd, isAdding, newTodoTitle, setNewTodoTitle, inputRef, todos, onToggleAll }) => { //+++


const handleSubmit = (event: React.FormEvent) => { //+++
Expand Down Expand Up @@ -39,6 +42,17 @@ export const Header: React.FC<HeaderProps> = ({ onAdd, isAdding, newTodoTitle, s
disabled={isAdding}
/>
</form>
</header>
{todos.length > 0 && (
<button
type="button"
data-cy="ToggleAllButton"
className={classNames('todoapp__toggle-all', {
active: todos.every(todo => todo.completed),
})}
onClick={onToggleAll}
>
</button>
)}
</header>
);
};
50 changes: 46 additions & 4 deletions src/components/TodoItem.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,72 @@
import React from 'react';
import React, { useState } from 'react';
import classNames from 'classnames';
import { Todo } from '../types/Todo';

interface TodoItemProps {
todo: Todo;
onDelete: (todoId: number) => void;
onUpdate: (updatedTodo: Todo) => void;
onToggleCompletion: (todoId: number) => void;
}

export const TodoItem: React.FC<TodoItemProps> = ({ todo, onDelete }) => {
export const TodoItem: React.FC<TodoItemProps> = ({ todo, onDelete, onUpdate, onToggleCompletion }) => {
const [isEdit, setIsEdit] = useState(false);
const [newTitle, setNewTitle] = useState(todo.title);

const isTemporary = todo.id === 0;

const handleTodoDoubleClick = () => {
setIsEdit(true);
}

return (
<div data-cy="Todo" key={todo.id} className={classNames('todo', { completed: todo.completed })}>
<div
data-cy="Todo"
key={todo.id}
className={classNames('todo', { completed: todo.completed })}
onDoubleClick={handleTodoDoubleClick}>
<label className="todo__status-label">
{ /* eslint-disable-next-line jsx-a11y/label-has-associated-control */ }
<input
data-cy="TodoStatus"
type="checkbox"
className="todo__status"
checked={todo.completed}
onChange={() => onToggleCompletion(todo.id)}
/>
</label>

<span data-cy="TodoTitle" className="todo__title">
{isEdit ? (
<form
onSubmit={(e) => {
e.preventDefault();
setIsEdit(false);
onUpdate({ ...todo, title: newTitle });
}}
>
<input
type="text"
className="todo__input"
onBlur={() => {
setIsEdit(false)
onUpdate({ ...todo, title: newTitle });
}}
value={newTitle}
onChange={event => setNewTitle(event.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
setIsEdit(false);
onUpdate({ ...todo, title: newTitle });
}
}}
autoFocus
/>
</form>
) : (
<span data-cy="TodoTitle" className="todo__title">
{todo.title}
</span>
)}

{/* Remove button appears only on hover */}
<button type="button" className="todo__remove" data-cy="TodoDelete" onClick={() => onDelete(todo.id)} disabled={isTemporary}>
Expand Down
7 changes: 4 additions & 3 deletions src/components/TodoList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ interface TodoListProps {
filter: FilterType;
onDelete: (todoId: number) => void;
onUpdate: (updatedTodo: Todo) => void;
onTodoDoubleClick: (todo: Todo) => void;
onToggleCompletion: (todoId: number) => void;
onToggleAll: () => void;
}

export const TodoList: React.FC<TodoListProps> = ({ todos, tempTodo, filter, onDelete, onTodoDoubleClick, onUpdate }) => {
export const TodoList: React.FC<TodoListProps> = ({ todos, tempTodo, filter, onDelete, onUpdate, onToggleCompletion }) => {

const filteredTodos = todos.filter(todo => {
if (filter === 'active') return !todo.completed;
Expand All @@ -30,8 +31,8 @@ export const TodoList: React.FC<TodoListProps> = ({ todos, tempTodo, filter, onD
key={todo.id ? todo.id : 'temp'}
todo={todo}
onDelete={onDelete}
onTodoDoubleClick={onTodoDoubleClick}
onUpdate={onUpdate}
onToggleCompletion={onToggleCompletion}
/>
))}
</section>
Expand Down
4 changes: 2 additions & 2 deletions src/styles/todoapp.scss
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@

&__toggle-all {
position: absolute;

top: 32px;
height: 100%;
width: 45px;

display: flex;
justify-content: center;
align-items: center;


font-size: 24px;
color: #e6e6e6;
Expand Down

0 comments on commit b37ff65

Please sign in to comment.