Skip to content

Commit

Permalink
Solution
Browse files Browse the repository at this point in the history
  • Loading branch information
MakksymSly committed Dec 17, 2024
1 parent edde475 commit fc26464
Show file tree
Hide file tree
Showing 5 changed files with 250 additions and 27 deletions.
14 changes: 14 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export const App: React.FC = () => {
const [isDeleteAllCompleted, setIsDeleteAllCompleted] =
useState<boolean>(false);

const [isUpdating, setIsUpdating] = useState(false);
const [updatingTodoId, setUpdatingTodoId] = useState<number | null>(null);
const [toggleCompleteAll, setToggleCompleteAll] = useState(false);

useEffect(() => {
setHasError(Errors.NoError);
let timer: NodeJS.Timeout | undefined;
Expand Down Expand Up @@ -95,6 +99,11 @@ export const App: React.FC = () => {
tempTodo={tempTodo}
inputRef={inputRef}
isDeleteAllCompleted={isDeleteAllCompleted}
isUpdating={isUpdating}
setIsUpdating={setIsUpdating}
updatingTodoId={updatingTodoId}
setUpdatingTodoId={setUpdatingTodoId}
setToggleCompleteAll={setToggleCompleteAll}
/>
<TodoList
todos={filteredTodos}
Expand All @@ -105,6 +114,11 @@ export const App: React.FC = () => {
setHasError={setHasError}
inputRef={inputRef}
initialTodos={todos}
isUpdating={isUpdating}
setIsUpdating={setIsUpdating}
updatingTodoId={updatingTodoId}
setUpdatingTodoId={setUpdatingTodoId}
toggleCompleteAll={toggleCompleteAll}
/>

{todos.length > 0 && (
Expand Down
2 changes: 1 addition & 1 deletion src/api/todos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ export const deleteTodo = (id: number) => {
return client.delete(`/todos/${id}`);
};

export const updateTodo = (id: number, data: Omit<Todo, 'id' | 'title'>) => {
export const updateTodo = (id: number, data: Omit<Todo, 'id'>) => {
return client.patch(`/todos/${id}`, data);
};
77 changes: 66 additions & 11 deletions src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { useEffect, useRef, useState } from 'react';
import { Errors } from '../../utils/Errors';
import { addTodo } from '../../api/todos';
import { addTodo, updateTodo } from '../../api/todos';
import { Todo } from '../../types/Todo';
import cn from 'classnames';

interface Props {
title: string;
Expand All @@ -15,6 +16,11 @@ interface Props {
tempTodo: Todo | null;
inputRef: React.RefObject<HTMLInputElement>;
isDeleteAllCompleted: boolean;
isUpdating: boolean;
setIsUpdating: (isUpdating: boolean) => void;
updatingTodoId: number | null;
setUpdatingTodoId: (updatingTodoId: number | null) => void;
setToggleCompleteAll: (toggleCompleteAll: boolean) => void;
}

export const Header: React.FC<Props> = props => {
Expand All @@ -29,11 +35,16 @@ export const Header: React.FC<Props> = props => {
tempTodo,
inputRef,
isDeleteAllCompleted,
setIsUpdating,
setUpdatingTodoId,
setToggleCompleteAll,
} = props;
const [isDisabled, setIsDisabled] = useState(false);
const [tempIdCounter, setTempIdCounter] = useState(0);

const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const isAllTodosCompleted = todos.every(todo => todo.completed);
const isTodosNotEmpty = todos.length !== 0;

const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
Expand All @@ -54,18 +65,18 @@ export const Header: React.FC<Props> = props => {
}

const newTodo = {
id: tempIdCounter,
userId: USER_ID,
title: title.trim(),
completed: false,
};

setTempTodo(newTodo);
setTempTodo({ ...newTodo, id: 0 });
setIsDisabled(true);

try {
await addTodo(newTodo);
setTodos([...todos, newTodo]);
const addNewTodo = await addTodo(newTodo);

setTodos([...todos, addNewTodo]);
setIsDisabled(false);
setTitle('');
setTempTodo(null);
Expand All @@ -90,21 +101,65 @@ export const Header: React.FC<Props> = props => {
inputRef.current?.focus();
}, [tempTodo, isDeleteAllCompleted]);

Check warning on line 102 in src/components/Header/Header.tsx

View workflow job for this annotation

GitHub Actions / run_linter (20.x)

React Hook useEffect has a missing dependency: 'inputRef'. Either include it or remove the dependency array

React.useEffect(() => {
useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);

const handleUpdateToCompleteAll = async () => {
setIsUpdating(true);
setUpdatingTodoId(null);
setToggleCompleteAll(true);

const areAllTodosCompleted = todos.every(todo => todo.completed);
const todosToUpdate = todos.filter(
todo => todo.completed === areAllTodosCompleted,
);

try {
const updatePromises = todosToUpdate.map(todo =>
updateTodo(todo.id, {
title: todo.title,
completed: !areAllTodosCompleted,
userId: todo.userId,
}),
);

await Promise.all(updatePromises);

const updatedTodos = todos.map(todo => ({
...todo,
completed: todosToUpdate.some(t => t.id === todo.id)
? !areAllTodosCompleted
: todo.completed,
}));

setTodos(updatedTodos);

setToggleCompleteAll(false);
setIsUpdating(false);
setUpdatingTodoId(null);
} catch {
setHasError(Errors.UnableToUpdate);
setIsUpdating(false);
setUpdatingTodoId(null);
setToggleCompleteAll(true);
}
};

return (
<header className="todoapp__header">
<button
type="button"
className="todoapp__toggle-all active"
data-cy="ToggleAllButton"
/>
{isTodosNotEmpty && (
<button
type="button"
className={cn('todoapp__toggle-all', { active: isAllTodosCompleted })}
data-cy="ToggleAllButton"
onClick={handleUpdateToCompleteAll}
/>
)}
<form onSubmit={handleSubmit}>
<input
data-cy="NewTodoField"
Expand Down
164 changes: 149 additions & 15 deletions src/components/TodoCard/TodoCard.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import React from 'react';
/* eslint-disable prettier/prettier */
import React, { useRef } from 'react';
import cn from 'classnames';
import { deleteTodo, updateTodo } from '../../api/todos';
import { Todo } from '../../types/Todo';
import { Errors } from '../../utils/Errors';



interface Props {
title: string;
isCompleted: boolean;
Expand All @@ -15,7 +19,15 @@ interface Props {
setHasError: (hasError: Errors) => void;
inputRef: React.RefObject<HTMLInputElement>;
initialTodos: Todo[];
isUpdating: boolean;
setIsUpdating: (isUpdating: boolean) => void;
updatingTodoId: number | null;
setUpdatingTodoId: (updatingTodoId: number | null) => void;
toggleCompleteAll: boolean;
}



/* eslint-disable jsx-a11y/label-has-associated-control */
export const TodoCard: React.FC<Props> = props => {
const {
Expand All @@ -29,11 +41,17 @@ export const TodoCard: React.FC<Props> = props => {
setHasError,
inputRef,
initialTodos,
isUpdating,
setIsUpdating,
updatingTodoId,
setUpdatingTodoId,
toggleCompleteAll,
} = props;

const [deletingCardId, setDeletingCardId] = React.useState<number | null>();
const [isUpdating, setIsUpdating] = React.useState(false);
const [updatingTodoId, setUpdatingTodoId] = React.useState<number | null>();
const [isEditStatus, setIsEditStatus] = React.useState(false);
const [editTitleQuery, setEditTitleQuery] = React.useState(title);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);

const handleDelete = async () => {
setIsDeleting(true);
Expand Down Expand Up @@ -62,6 +80,7 @@ export const TodoCard: React.FC<Props> = props => {
try {
if (currTodo) {
await updateTodo(currTodo.id, {
title: currTodo.title,
completed: !currTodo.completed,
userId: currTodo.userId,
});
Expand All @@ -81,6 +100,90 @@ export const TodoCard: React.FC<Props> = props => {
}
};

const handleEditUpdate = async (
event:
| React.FormEvent<HTMLFormElement>
| React.FocusEvent<HTMLInputElement, Element>
| React.KeyboardEvent<HTMLInputElement>
) => {
event.preventDefault();

const currTodo = initialTodos.find(todo => todo.id === todoId);

if (currTodo) {
setIsUpdating(true);
setUpdatingTodoId(currTodo.id);
}


if(!editTitleQuery.trim()) {
handleDelete();
setIsUpdating(false);

return;
}

if (editTitleQuery === currTodo?.title) {
setIsUpdating(false);
setIsEditStatus(false);

return;
}

if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}





try {
if (currTodo) {
const todoToUpdate = {
title: editTitleQuery.trim(),
completed: currTodo.completed,
userId: currTodo.userId,
};

await updateTodo(currTodo.id, todoToUpdate);

setIsUpdating(false);
setUpdatingTodoId(null);
setTodos(initialTodos.map(todo =>
(todo.id === currTodo.id ? { ...todoToUpdate, id: todo.id } : todo)));
setIsEditStatus(false);

}
} catch {

setHasError(Errors.UnableToUpdate);
setIsUpdating(false);
setUpdatingTodoId(null);

if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}

timeoutRef.current = setTimeout(() => {
setHasError(Errors.NoError);
}, 3000);
}
};

const handleEscapeOrEnterButton =
(event: React.KeyboardEvent<HTMLInputElement>) => {

if (event.key === 'Escape') {
setIsEditStatus(false);
setEditTitleQuery(title);
}

if (event.key === 'Enter') {
handleEditUpdate(event);
}
};

return (
<div data-cy="Todo" className={cn('todo', { completed: isCompleted })}>
<label className="todo__status-label">
Expand All @@ -93,26 +196,57 @@ export const TodoCard: React.FC<Props> = props => {
/>
</label>

<span data-cy="TodoTitle" className="todo__title">
{title}
</span>
{isEditStatus && (
<form onSubmit={handleEditUpdate}>
<input
data-cy="TodoTitleField"
type="text"
className="todo__title-field"
placeholder="Empty todo will be deleted"
value={editTitleQuery}
onChange={event => setEditTitleQuery(event.target.value)}
autoFocus
onBlur={event => {
handleEditUpdate(event);
setIsEditStatus(false);
}}
onKeyDown={handleEscapeOrEnterButton}

<button
type="button"
className="todo__remove"
data-cy="TodoDelete"
onClick={handleDelete}
>
×
</button>
/>
</form>
)}

{!isEditStatus && (
<>
<span
data-cy="TodoTitle"
className="todo__title"
onDoubleClick={() => {
setIsEditStatus(true);
}}
>
{title}
</span>

<button
type="button"
className="todo__remove"
data-cy="TodoDelete"
onClick={handleDelete}
>
×
</button>
</>
)}

<div
data-cy="TodoLoader"
className={cn('modal overlay', {
'is-active':
isTempTodo ||
(isDeleting && deletingCardId === todoId) ||
(isUpdating && updatingTodoId === todoId),
(isUpdating && updatingTodoId === todoId) ||
toggleCompleteAll,
})}
>
<div className="modal-background has-background-white-ter" />
Expand Down
Loading

0 comments on commit fc26464

Please sign in to comment.