Skip to content

Commit

Permalink
Fix: fix including part2 PR review
Browse files Browse the repository at this point in the history
  • Loading branch information
MakksymSly committed Dec 19, 2024
1 parent a0fcdbb commit 0049c5c
Show file tree
Hide file tree
Showing 12 changed files with 188 additions and 203 deletions.
153 changes: 124 additions & 29 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
/* eslint-disable max-len */
/* eslint-disable jsx-a11y/control-has-associated-label */
/* eslint-disable jsx-a11y/label-has-associated-control */
import React, { useEffect, useRef, useState } from 'react';
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { Header } from './components/Header/Header';
import { Footer } from './components/Footer/Footer';
import { TodoList } from './components/TodoList/TodoList';
import { Todo } from './types/Todo';
import { deleteTodo, getTodos, USER_ID } from './api/todos';
import { Errors } from './utils/Errors';
import { FilterTodosBy } from './utils/FilterTodosBy';
import { addTodo, deleteTodo, getTodos, updateTodo } from './api/todos';
import { Errors } from './types/Errors';
import { FilterTodosBy } from './types/FilterTodosBy';
import { Error } from './components/Error/Error';
import { filterTodos } from './utils/utils';

export const App: React.FC = () => {
const [todos, setTodos] = useState<Todo[]>([]);
Expand All @@ -22,6 +29,9 @@ export const App: React.FC = () => {
const [isUpdating, setIsUpdating] = useState(false);
const [updatingTodoId, setUpdatingTodoId] = useState<number | null>(null);
const [toggleCompleteAll, setToggleCompleteAll] = useState(false);
const [isHeaderDisabled, setIsHeaderDisabled] = useState(false);
const [tempIdCounter, setTempIdCounter] = useState(0);
const [deletingCardId, setDeletingCardId] = useState<number | null>(null);

useEffect(() => {
setHasError(Errors.NoError);
Expand All @@ -37,23 +47,55 @@ export const App: React.FC = () => {
})();
}, []);

const filteredTodos = todos.filter(todo => {
switch (filterBy) {
case FilterTodosBy.Active:
return !todo.completed;
case FilterTodosBy.Completed:
return todo.completed;
default:
return true;
}
});
const filteredTodos = filterTodos(todos, filterBy);

const uncompletedTodosLength = useMemo(() => {
return todos.filter(todo => !todo.completed).length;
}, [todos]);

const uncompletedTodosLength = todos.filter(todo => !todo.completed).length;
const completedTodosLenght = todos.filter(todo => todo.completed).length;
const completedTodosLength = useMemo(() => {
return todos.filter(todo => todo.completed).length;
}, [todos]);

const inputRef = useRef<HTMLInputElement | null>(null);

const handleDeleteAllCompleted = async () => {
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
setHasError(Errors.NoError);

if (!title.trim()) {
setHasError(Errors.TitleEmpty);

return;
}

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

setTempTodo({ ...newTodo, id: tempIdCounter });
setIsHeaderDisabled(true);

try {
const addNewTodo = await addTodo(newTodo);

setTodos([...todos, addNewTodo]);
setIsHeaderDisabled(false);
setTitle('');
setTempTodo(null);
setTempIdCounter(tempIdCounter + 1);

inputRef.current?.focus();
} catch {
setHasError(Errors.UnableToAdd);
setTempTodo(null);
setIsHeaderDisabled(false);
}
};

const handleDeleteAllCompleted = useCallback(async () => {
const completedTodos = todos.filter(todo => todo.completed);

for (const todo of completedTodos) {
Expand All @@ -66,6 +108,65 @@ export const App: React.FC = () => {
setHasError(Errors.UnableToDelete);
}
}
}, [todos]);

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);
}
};

const handleDelete = async (todoId: number) => {
setIsDeleting(true);
setDeletingCardId(todoId);

try {
await deleteTodo(todoId);
setIsDeleting(false);
setDeletingCardId(null);
setTodos(todos.filter(todo => todo.id !== todoId));
setDeletingCardId(null);
inputRef.current?.focus();
} catch {
setHasError(Errors.UnableToDelete);
setIsDeleting(false);
setDeletingCardId(null);
}
};

return (
Expand All @@ -75,18 +176,11 @@ export const App: React.FC = () => {
<Header
title={title}
setTitle={setTitle}
setHasError={setHasError}
hasError={hasError}
USER_ID={USER_ID}
todos={todos}
setTodos={setTodos}
setTempTodo={setTempTodo}
inputRef={inputRef}
isUpdating={isUpdating}
setIsUpdating={setIsUpdating}
updatingTodoId={updatingTodoId}
setUpdatingTodoId={setUpdatingTodoId}
setToggleCompleteAll={setToggleCompleteAll}
isHeaderDisabled={isHeaderDisabled}
handleSubmit={handleSubmit}
handleUpdateToCompleteAll={handleUpdateToCompleteAll}
/>
<TodoList
todos={filteredTodos}
Expand All @@ -95,21 +189,22 @@ export const App: React.FC = () => {
setIsDeleting={setIsDeleting}
setTodos={setTodos}
setHasError={setHasError}
inputRef={inputRef}
initialTodos={todos}
isUpdating={isUpdating}
setIsUpdating={setIsUpdating}
updatingTodoId={updatingTodoId}
setUpdatingTodoId={setUpdatingTodoId}
toggleCompleteAll={toggleCompleteAll}
handleDelete={handleDelete}
deletingCardId={deletingCardId}
/>

{todos.length > 0 && (
<Footer
uncompletedTodosLength={uncompletedTodosLength}
filterBy={filterBy}
setFilteredBy={setFilterBy}
completedTodosLenght={completedTodosLenght}
completedTodosLenght={completedTodosLength}
handleDeleteAllCompleted={handleDeleteAllCompleted}
/>
)}
Expand Down
9 changes: 5 additions & 4 deletions src/api/todos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@ import { Todo } from '../types/Todo';
import { client } from '../utils/fetchClient';

export const USER_ID = 2142;
const TODOS_PATH = '/todos';

export const getTodos = () => {
return client.get<Todo[]>(`/todos?userId=${USER_ID}`);
return client.get<Todo[]>(`${TODOS_PATH}?userId=${USER_ID}`);
};

export const addTodo = (data: Omit<Todo, 'id'>) => {
return client.post<Todo>('/todos', data);
return client.post<Todo>(`${TODOS_PATH}`, data);
};

export const deleteTodo = (id: number) => {
return client.delete(`/todos/${id}`);
return client.delete(`${TODOS_PATH}/${id}`);
};

export const updateTodo = (id: number, data: Omit<Todo, 'id'>) => {
return client.patch(`/todos/${id}`, data);
return client.patch(`${TODOS_PATH}/${id}`, data);
};
2 changes: 1 addition & 1 deletion src/components/Error/Error.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect } from 'react';
import cn from 'classnames';
import { Errors } from '../../utils/Errors';
import { Errors } from '../../types/Errors';

interface Props {
hasError: Errors;
Expand Down
21 changes: 8 additions & 13 deletions src/components/Footer/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { FilterTodosBy } from '../../utils/FilterTodosBy';
import { FilterTodosBy } from '../../types/FilterTodosBy';
import cn from 'classnames';
interface Props {
uncompletedTodosLength: number;
Expand All @@ -17,31 +17,26 @@ export const Footer: React.FC<Props> = props => {
handleDeleteAllCompleted,
} = props;

const filters = [
{ label: 'All', value: FilterTodosBy.All, href: '#/' },
{ label: 'Active', value: FilterTodosBy.Active, href: '#/active' },
{ label: 'Completed', value: FilterTodosBy.Completed, href: '#/completed' },
];
const filters = Object.values(FilterTodosBy);

return (
<footer className="todoapp__footer" data-cy="Footer">
<span className="todo-count" data-cy="TodosCounter">
{uncompletedTodosLength} items left
</span>

{/* Active link should have the 'selected' class */}
<nav className="filter" data-cy="Filter">
{filters.map(filter => (
<a
key={filter.value}
href={filter.href}
key={filter}
href={`/#/${filter === FilterTodosBy.All ? '' : filter.toLowerCase()}`}
className={cn('filter__link', {
selected: filterBy === filter.value,
selected: filterBy === filter,
})}
data-cy={`FilterLink${filter.label}`}
onClick={() => setFilteredBy(filter.value)}
data-cy={`FilterLink${filter}`}
onClick={() => setFilteredBy(filter)}
>
{filter.label}
{filter}
</a>
))}
</nav>
Expand Down
Loading

0 comments on commit 0049c5c

Please sign in to comment.