diff --git a/src/App.tsx b/src/App.tsx index d46111e825..f57bb5e74d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ /* eslint-disable max-len */ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import 'bulma/css/bulma.css'; import '@fortawesome/fontawesome-free/css/all.css'; @@ -7,28 +7,65 @@ import { TodoList } from './components/TodoList'; import { TodoFilter } from './components/TodoFilter'; import { TodoModal } from './components/TodoModal'; import { Loader } from './components/Loader'; +import { getTodos } from './api'; +import { Todo } from './types/Todo'; +import { filterTodos } from './utils/filterTodos'; +import { FilterByState } from './types/FilterByState'; export const App: React.FC = () => { + const [todoList, setTodoList] = useState([]); + const [isLoading, setLoading] = useState(false); + const [selectedTodo, setSelectedTodo] = useState(null); + const [filterBy, setFilterBy] = useState(FilterByState.ALL); + const [query, setQuery] = useState(''); + const [hasErrorMessage, setErrorMessage] = useState(''); + + useEffect(() => { + setLoading(true); + getTodos() + .then(setTodoList) + .catch(error => setErrorMessage(error.message)) + .finally(() => setLoading(false)); + }, []); + + const filteredTodos = filterTodos(todoList, filterBy, query); + return ( <>
-

Todos:

+ {hasErrorMessage &&

{hasErrorMessage}

}
- +
- - + {isLoading && } + + {!isLoading && !hasErrorMessage && ( + + )}
- + {selectedTodo && ( + + )} ); }; diff --git a/src/components/TodoFilter/TodoFilter.tsx b/src/components/TodoFilter/TodoFilter.tsx index 193f1cd2b2..12816cab08 100644 --- a/src/components/TodoFilter/TodoFilter.tsx +++ b/src/components/TodoFilter/TodoFilter.tsx @@ -1,30 +1,57 @@ -export const TodoFilter = () => ( -
-

- - - -

+import { FilterByState } from '../../types/FilterByState'; -

- - - - +type Props = { + query: string; + setQuery: (query: string) => void; + setFilterBy: (filterBy: FilterByState) => void; +}; - - {/* eslint-disable-next-line jsx-a11y/control-has-associated-label */} - + + + ); +}; diff --git a/src/components/TodoItem.tsx/index.ts b/src/components/TodoItem.tsx/index.ts new file mode 100644 index 0000000000..21f4abac39 --- /dev/null +++ b/src/components/TodoItem.tsx/index.ts @@ -0,0 +1 @@ +export * from './TodoItem'; diff --git a/src/components/TodoList/TodoList.tsx b/src/components/TodoList/TodoList.tsx index 84dbcf3c0e..022802255d 100644 --- a/src/components/TodoList/TodoList.tsx +++ b/src/components/TodoList/TodoList.tsx @@ -1,6 +1,18 @@ import React from 'react'; +import { Todo } from '../../types/Todo'; +import { TodoItem } from '../TodoItem.tsx'; -export const TodoList: React.FC = () => ( +type Props = { + todoList: Todo[]; + setSelectedTodo: (todo: Todo | null) => void; + selectedTodo: Todo | null; +}; + +export const TodoList: React.FC = ({ + todoList, + setSelectedTodo, + selectedTodo, +}) => ( @@ -16,85 +28,14 @@ export const TodoList: React.FC = () => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + {todoList.map(todo => ( + + ))}
1 - -

delectus aut autem

-
- -
2 - -

quis ut nam facilis et officia qui

-
- -
1 - -

delectus aut autem

-
- -
6 - -

- qui ullam ratione quibusdam voluptatem quia omnis -

-
- -
8 - - - - -

quo adipisci enim quam ut ab

-
- -
); diff --git a/src/components/TodoModal/TodoModal.tsx b/src/components/TodoModal/TodoModal.tsx index 0fbe6dae99..aa08075843 100644 --- a/src/components/TodoModal/TodoModal.tsx +++ b/src/components/TodoModal/TodoModal.tsx @@ -1,39 +1,78 @@ -import React from 'react'; +import { useEffect, useState } from 'react'; import { Loader } from '../Loader'; +import { Todo } from '../../types/Todo'; +import { User } from '../../types/User'; +import { getUser } from '../../api'; + +type Props = { + selectedTodo: Todo; + setSelectedTodo: (todo: Todo | null) => void; +}; + +export const TodoModal: React.FC = ({ + selectedTodo, + setSelectedTodo, +}) => { + const [isLoading, setIsLoading] = useState(false); + const [userData, setUserData] = useState(null); + const [hasErrorMessage, setErrorMessage] = useState(''); + + const userId = selectedTodo.userId; + + useEffect(() => { + setIsLoading(true); + + if (userId) { + getUser(userId) + .then(data => setUserData(data)) + .catch(e => setErrorMessage(e.message)) + .finally(() => setIsLoading(false)); + } + }, [userId]); -export const TodoModal: React.FC = () => { return (

- {true ? ( + {isLoading ? ( ) : (
+ {hasErrorMessage &&

{hasErrorMessage}

}
- Todo #2 + Todo #{selectedTodo.id}
{/* eslint-disable-next-line jsx-a11y/control-has-associated-label */} -

- quis ut nam facilis et officia qui + {selectedTodo.title}

- {/* Done */} - Planned + {selectedTodo.completed ? ( + Done + ) : ( + Planned + )} {' by '} - Leanne Graham + {userData && ( + {userData.name} + )}

diff --git a/src/types/FilterByState.ts b/src/types/FilterByState.ts new file mode 100644 index 0000000000..f223935859 --- /dev/null +++ b/src/types/FilterByState.ts @@ -0,0 +1,5 @@ +export enum FilterByState { + ACTIVE = 'active', + COMPLETED = 'completed', + ALL = 'all', +} diff --git a/src/utils/filterTodos.ts b/src/utils/filterTodos.ts new file mode 100644 index 0000000000..f85a59468f --- /dev/null +++ b/src/utils/filterTodos.ts @@ -0,0 +1,26 @@ +import { FilterByState } from '../types/FilterByState'; +import { Todo } from '../types/Todo'; + +export function filterTodos( + todos: Todo[], + filterBy: FilterByState, + query: string, +): Todo[] { + return todos + .filter(todo => { + if (filterBy === FilterByState.ACTIVE) { + return !todo.completed; + } + + if (filterBy === FilterByState.COMPLETED) { + return todo.completed; + } + + return true; + }) + .filter(todo => { + const formatQuery = query.toLowerCase().trim(); + + return todo.title.toLowerCase().includes(formatQuery); + }); +}