From 4eca8ebc2edab4a2022944a0360aed08ad5744a0 Mon Sep 17 00:00:00 2001 From: Diana Darienko Date: Thu, 19 Dec 2024 22:26:20 +0200 Subject: [PATCH] updated --- src/App.tsx | 156 +++++++----------- .../UserWarning => }/UserWarning.tsx | 0 .../ErrorNotification/ErrorNotification.tsx | 27 +++ src/components/ErrorNotification/index.js | 1 + src/components/TempTodo/TempTodo.tsx | 35 ++++ src/components/TempTodo/index.js | 1 + src/components/TodoFooter/TodoFooter.tsx | 24 +-- src/components/TodoForm/TodoForm.tsx | 45 +++-- src/components/TodoHeader/TodoHeader.tsx | 53 +++--- src/components/TodoItem/TodoItem.tsx | 96 ++++------- src/components/TodoList/TodoList.tsx | 30 ++-- src/components/UserWarning/index.js | 1 - 12 files changed, 232 insertions(+), 237 deletions(-) rename src/{components/UserWarning => }/UserWarning.tsx (100%) create mode 100644 src/components/ErrorNotification/ErrorNotification.tsx create mode 100644 src/components/ErrorNotification/index.js create mode 100644 src/components/TempTodo/TempTodo.tsx create mode 100644 src/components/TempTodo/index.js delete mode 100644 src/components/UserWarning/index.js diff --git a/src/App.tsx b/src/App.tsx index 038f6c664c..36c309db75 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,23 +1,22 @@ /* eslint-disable jsx-a11y/label-has-associated-control */ /* eslint-disable jsx-a11y/control-has-associated-label */ -import React, { useEffect, useMemo, useState } from 'react'; -import { UserWarning } from './components/UserWarning/UserWarning'; +import React, { useEffect, useState } from 'react'; +import { UserWarning } from './UserWarning'; import { getTodos, USER_ID } from './api/todos'; import { Todo } from './types/Todo'; import { TodoFilter } from './types/TodoFilter'; -import classNames from 'classnames'; import { TodoList } from './components/TodoList'; import { TodoFooter } from './components/TodoFooter'; import * as todoServices from './api/todos'; import { TodoHeader } from './components/TodoHeader'; +import { ErrorNotification } from './components/ErrorNotification'; export const App: React.FC = () => { const [todos, setTodos] = useState([]); const [error, setError] = useState(''); const [filter, setFilter] = useState(TodoFilter.All); const [tempTodo, setTempTodo] = useState(null); - const [loading, setLoading] = useState(false); - const [loadingId, setLoadingId] = useState(0); + const [todosInProcess, setTodosInProcess] = useState([0]); // #region loadTodos const loadTodos = async () => { @@ -56,111 +55,98 @@ export const App: React.FC = () => { } }); - const completedTodos = useMemo(() => { - return todos.filter(todo => todo.completed); - }, [todos]); - if (!USER_ID) { return ; } // #endregion - + // #region todoServices const addTodo = async (newTodo: Todo) => { - setLoading(true); - - setLoadingId(newTodo.id); - try { const addedTodo = await todoServices.addTodo(newTodo); setTodos(prevTodos => [...prevTodos].concat(addedTodo)); } finally { setTempTodo(null); - setLoading(false); } }; const deleteTodo = async (id: number | number[]) => { - setLoading(true); - const ids = Array.isArray(id) ? id : [id]; - setLoadingId(ids); + setTodosInProcess(currentTodo => [...currentTodo, ...ids]); try { - const deletePromise = ids.map(async i => { - try { - await todoServices.deleteTodo(i); + const results = await Promise.all( + ids.map(async i => { + try { + await todoServices.deleteTodo(i); - return i; - } catch { - setError('Unable to delete a todo'); + return i; + } catch { + setError('Unable to delete a todo'); - return null; - } - }); - const results = await Promise.all(deletePromise); + return null; + } + }), + ); const successDelete = results.filter(result => result !== null); setTodos(todos.filter(todo => !successDelete.includes(todo.id))); } finally { - setLoading(false); + setTodosInProcess(currentId => currentId.filter(i => !ids.includes(i))); } }; const updateTodo = async (updatedTodo: Todo | Todo[]) => { - setLoading(true); - - const ids = !Array.isArray(updatedTodo) - ? updatedTodo.id - : updatedTodo.map(todo => todo.id); - - setLoadingId(ids); - - const handleUpdatedTodo = ({ - title, - id, - completed, - }: Omit) => { - return todoServices.updatedTodo({ - title, - id, - completed, - }); + const ids = Array.isArray(updatedTodo) + ? updatedTodo.map(todo => todo.id) + : [updatedTodo.id]; + + setTodosInProcess(currentTodo => [...currentTodo, ...ids]); + + const handleUpdatedTodo = async (todo: Omit) => { + return todoServices.updatedTodo(todo); }; try { + let updatedTodos: Todo[] = []; + if (Array.isArray(updatedTodo)) { - const updatedTodos = await Promise.all( - updatedTodo.map(todo => handleUpdatedTodo(todo)), - ); - - setTodos(prevTodos => - prevTodos.map(prevTodo => { - const updatedTodoItem = updatedTodos.find( - todo => prevTodo.id === todo.id, - ); - - return updatedTodoItem ? updatedTodoItem : prevTodo; - }), - ); + const updatePromises = updatedTodo.map(todo => handleUpdatedTodo(todo)); + const results = await Promise.allSettled(updatePromises); + + updatedTodos = results + .filter( + (result): result is PromiseFulfilledResult => + result.status === 'fulfilled', + ) + .map(result => result.value); } else { - await handleUpdatedTodo(updatedTodo); - setTodos(prevTodos => - prevTodos.map(todo => - todo.id === updatedTodo.id ? updatedTodo : todo, - ), - ); + const updated = await handleUpdatedTodo(updatedTodo); + + updatedTodos = updated ? [updated] : []; } + + setTodos(prevTodos => + prevTodos.map(prevTodo => { + const updatedTodoItem = updatedTodos.find( + todo => prevTodo.id === todo.id, + ); + + return updatedTodoItem ? updatedTodoItem : prevTodo; + }), + ); } catch { setError('Unable to update a todo'); } finally { - setLoading(false); + setTodosInProcess(currentId => currentId.filter(id => !ids.includes(id))); } }; + // #endregion + return (

todos

@@ -169,50 +155,32 @@ export const App: React.FC = () => { - {/* Hide the footer if there are no todos */} - - {todos.length > 0 && ( + {(todos.length > 0 || tempTodo) && ( )} - {todos.length > 0 && ( + {(todos.length > 0 || tempTodo) && ( )}
-
-
+ ); }; diff --git a/src/components/UserWarning/UserWarning.tsx b/src/UserWarning.tsx similarity index 100% rename from src/components/UserWarning/UserWarning.tsx rename to src/UserWarning.tsx diff --git a/src/components/ErrorNotification/ErrorNotification.tsx b/src/components/ErrorNotification/ErrorNotification.tsx new file mode 100644 index 0000000000..a2aa93f28b --- /dev/null +++ b/src/components/ErrorNotification/ErrorNotification.tsx @@ -0,0 +1,27 @@ +import classNames from 'classnames'; +import React from 'react'; + +interface Props { + error: string; + onError: (error: string) => void; +} + +export const ErrorNotification: React.FC = ({ error, onError }) => { + return ( +
+
+ ); +}; diff --git a/src/components/ErrorNotification/index.js b/src/components/ErrorNotification/index.js new file mode 100644 index 0000000000..8cb4787920 --- /dev/null +++ b/src/components/ErrorNotification/index.js @@ -0,0 +1 @@ +export * from './ErrorNotification'; diff --git a/src/components/TempTodo/TempTodo.tsx b/src/components/TempTodo/TempTodo.tsx new file mode 100644 index 0000000000..cd921c520f --- /dev/null +++ b/src/components/TempTodo/TempTodo.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { Todo } from '../../types/Todo'; + +interface Props { + tempTitle: Todo; +} + +export const TempTodo: React.FC = ({ tempTitle: { title } }) => { + return ( +
+ + + + {title} + + + +
+
+
+
+
+ ); +}; diff --git a/src/components/TempTodo/index.js b/src/components/TempTodo/index.js new file mode 100644 index 0000000000..85671cecaa --- /dev/null +++ b/src/components/TempTodo/index.js @@ -0,0 +1 @@ +export * from './TempTodo'; diff --git a/src/components/TodoFooter/TodoFooter.tsx b/src/components/TodoFooter/TodoFooter.tsx index 459a8f35a6..e3488f631c 100644 --- a/src/components/TodoFooter/TodoFooter.tsx +++ b/src/components/TodoFooter/TodoFooter.tsx @@ -1,30 +1,35 @@ import classNames from 'classnames'; -import React from 'react'; +import React, { useMemo } from 'react'; import { Todo } from '../../types/Todo'; import { TodoFilter } from '../../types/TodoFilter'; interface Props { - completedTodos: Todo[] | []; + todos: Todo[]; filter: string; - onChange: (filter: TodoFilter) => void; + onFilter: (filter: TodoFilter) => void; onDelete: (arrayId: number[]) => Promise; - activeItems: number; } export const TodoFooter: React.FC = ({ - completedTodos, + todos, filter, - onChange, + onFilter, onDelete, - activeItems, }) => { + const completedTodos = useMemo(() => { + return todos.filter(todo => todo.completed); + }, [todos]); + + const activeItems = useMemo(() => { + return todos.length - completedTodos.length; + }, [completedTodos, todos]); + return (