-
Todos:
+export const App = () => {
+ const dispatch = useDispatch();
+ const todos = useSelector((state: RootState) => state.todos);
+ const { query, status } = useSelector((state: RootState) => state.filter);
+ const selectedTodo = useSelector((state: RootState) => state.currentTodo);
-
-
-
+ const [isLoading, setIsLoading] = useState(false);
+
+ useEffect(() => {
+ setIsLoading(true);
+ getTodos()
+ .then(readyTodos => dispatch(todosActions.setTodos(readyTodos)))
+ .finally(() => setIsLoading(false));
+ }, [dispatch]);
+
+ const readyTodos = prepareTodos(todos, query, status);
+
+ return (
+ <>
+
+
+
+
Todos:
+
+
+
+ dispatch(filterActions.setQuery(newQuery))
+ }
+ setStatus={newStatus =>
+ dispatch(filterActions.setStatus(newStatus))
+ }
+ />
+
+
+
+ {isLoading &&
}
-
-
-
+ {!isLoading && todos.length > 0 && (
+
+ dispatch(currentTodoActions.setCurrentTodo(newSelectedTodo))
+ }
+ />
+ )}
+
-
-
- >
-);
+ {selectedTodo && (
+
+ dispatch(currentTodoActions.setCurrentTodo(newSelectedTodo))
+ }
+ />
+ )}
+ >
+ );
+};
diff --git a/src/app/store.ts b/src/app/store.ts
index 3a9b384be..d390bac6e 100644
--- a/src/app/store.ts
+++ b/src/app/store.ts
@@ -1,6 +1,9 @@
import { combineSlices, configureStore } from '@reduxjs/toolkit';
+import { currentTodoSlice } from '../features/currentTodo';
+import { filterSlice } from '../features/filter';
+import { todosSlice } from '../features/todos';
-const rootReducer = combineSlices();
+const rootReducer = combineSlices(todosSlice, filterSlice, currentTodoSlice);
export const store = configureStore({
reducer: rootReducer,
diff --git a/src/components/TodoFilter/TodoFilter.tsx b/src/components/TodoFilter/TodoFilter.tsx
index c1b574ee7..b147857a6 100644
--- a/src/components/TodoFilter/TodoFilter.tsx
+++ b/src/components/TodoFilter/TodoFilter.tsx
@@ -1,6 +1,17 @@
import React from 'react';
+import { Status } from '../../tools/constants';
-export const TodoFilter: React.FC = () => {
+type Props = {
+ query: string;
+ setStatus: (filterField: string) => void;
+ setTitle: (title: string) => void;
+};
+
+export const TodoFilter: React.FC = ({
+ query,
+ setStatus: setFilterField,
+ setTitle,
+}) => {
return (
diff --git a/src/components/TodoList/TodoList.tsx b/src/components/TodoList/TodoList.tsx
index 5089eb030..bb6c86c77 100644
--- a/src/components/TodoList/TodoList.tsx
+++ b/src/components/TodoList/TodoList.tsx
@@ -1,224 +1,128 @@
-/* eslint-disable */
import React from 'react';
+import { Todo } from '../../types/Todo';
+import classNames from 'classnames';
-export const TodoList: React.FC = () => {
+type Props = {
+ todos: Todo[];
+ selectedTodo: Todo | null;
+ setSelectedTodo: (selectedTodo: Todo | null) => void;
+};
+
+export const TodoList: React.FC = ({
+ todos,
+ selectedTodo,
+ setSelectedTodo,
+}) => {
return (
<>
-
- There are no todos matching current filter criteria
-
-
-
-
-
- # |
-
-
-
-
-
- |
-
- Title |
- |
-
-
-
-
-
- 1 |
- |
-
-
- delectus aut autem
- |
-
-
-
- |
-
-
-
- 2 |
- |
-
-
-
- quis ut nam facilis et officia qui
-
- |
-
-
-
- |
-
-
-
- 3 |
- |
-
-
- fugiat veniam minus
- |
-
-
-
- |
-
-
-
- 4 |
-
-
-
-
- |
-
- et porro tempora
- |
-
-
- |
+
+
+
+ {todos.map(todo => (
+
+ {todo.id} |
+
+ {todo.completed && (
+
+
+
+ )}
+ |
+
+
+
+ {todo.title}
+
+ |
+
+
+ setSelectedTodo(todo)}
+ >
+
+
+
+
+ |
+
+ ))}
+
+
+ )}
>
);
};
+
+{
+ /*
+1 |
+ |
+
+
+ delectus aut autem
+ |
+
+
+
+
+
+
+
+ |
+
+
+
+3 |
+ |
+
+
+ fugiat veniam minus
+ |
+
+
+
+
+
+
+
+ |
+
*/
+}
diff --git a/src/components/TodoModal/TodoModal.tsx b/src/components/TodoModal/TodoModal.tsx
index e16a079d5..a4f4094a3 100644
--- a/src/components/TodoModal/TodoModal.tsx
+++ b/src/components/TodoModal/TodoModal.tsx
@@ -1,42 +1,71 @@
-import React from 'react';
+import React, { useEffect, useState } from 'react';
import { Loader } from '../Loader';
+import { Todo } from '../../types/Todo';
+import { getUser } from '../../api';
+import { User } from '../../types/User';
+
+type Props = {
+ selectedTodo: Todo;
+ setSelectedTodo: (selectedTodo: Todo | null) => void;
+};
+
+export const TodoModal: React.FC = ({
+ selectedTodo,
+ setSelectedTodo,
+}) => {
+ const [user, setUser] = useState();
+ const [userLoading, setUserLoading] = useState(false);
+
+ useEffect(() => {
+ setUserLoading(true);
+ getUser(selectedTodo.userId)
+ .then(setUser)
+ .finally(() => setUserLoading(false));
+ }, [selectedTodo]);
-export const TodoModal: React.FC = () => {
return (
-
+ {userLoading ? (
+
+ ) : (
+
);
};
diff --git a/src/features/currentTodo.ts b/src/features/currentTodo.ts
index 9e69e2240..94fe8cdad 100644
--- a/src/features/currentTodo.ts
+++ b/src/features/currentTodo.ts
@@ -1,4 +1,4 @@
-import { createSlice } from '@reduxjs/toolkit';
+import { PayloadAction, createSlice } from '@reduxjs/toolkit';
import { Todo } from '../types/Todo';
const initialState = null as Todo | null;
@@ -6,5 +6,14 @@ const initialState = null as Todo | null;
export const currentTodoSlice = createSlice({
name: 'currentTodo',
initialState,
- reducers: {},
+ reducers: {
+ setCurrentTodo: (_, action: PayloadAction
) => {
+ return action.payload;
+ },
+ clearCurrentTodo: () => {
+ return null;
+ },
+ },
});
+
+export const currentTodoActions = currentTodoSlice.actions;
diff --git a/src/features/filter.ts b/src/features/filter.ts
index d0eaf4640..f2bf5d164 100644
--- a/src/features/filter.ts
+++ b/src/features/filter.ts
@@ -1,4 +1,4 @@
-import { createSlice } from '@reduxjs/toolkit';
+import { PayloadAction, createSlice } from '@reduxjs/toolkit';
const initialState = {
query: '',
@@ -8,5 +8,17 @@ const initialState = {
export const filterSlice = createSlice({
name: 'filter',
initialState,
- reducers: {},
+ reducers: {
+ setQuery: (state, action: PayloadAction) => {
+ return { ...state, query: action.payload };
+ },
+ setStatus: (state, action: PayloadAction) => {
+ return { ...state, status: action.payload };
+ },
+ clearQuery: state => {
+ return { ...state, query: '', status: 'all' };
+ },
+ },
});
+
+export const filterActions = filterSlice.actions;
diff --git a/src/features/todos.ts b/src/features/todos.ts
index 4555dea8d..7c66cc993 100644
--- a/src/features/todos.ts
+++ b/src/features/todos.ts
@@ -1,8 +1,14 @@
-import { createSlice } from '@reduxjs/toolkit';
+import { PayloadAction, createSlice } from '@reduxjs/toolkit';
import { Todo } from '../types/Todo';
export const todosSlice = createSlice({
name: 'todos',
initialState: [] as Todo[],
- reducers: {},
+ reducers: {
+ setTodos(todos, action: PayloadAction) {
+ todos.push(...action.payload);
+ },
+ },
});
+
+export const todosActions = todosSlice.actions;
diff --git a/src/tools/constants.ts b/src/tools/constants.ts
new file mode 100644
index 000000000..82183d905
--- /dev/null
+++ b/src/tools/constants.ts
@@ -0,0 +1,5 @@
+export enum Status {
+ ALL = 'all',
+ ACTIVE = 'active',
+ COMPLETED = 'completed',
+}
diff --git a/src/tools/prepareTodos.tsx b/src/tools/prepareTodos.tsx
new file mode 100644
index 000000000..7e11a4109
--- /dev/null
+++ b/src/tools/prepareTodos.tsx
@@ -0,0 +1,32 @@
+import { Todo } from '../types/Todo';
+import { Status } from './constants';
+
+export const prepareTodos = (
+ readyTodos: Todo[],
+ query: string,
+ status: string,
+) => {
+ return readyTodos.filter((todo: Todo) => {
+ const titleCondition = todo.title
+ .toLowerCase()
+ .includes(query.toLowerCase());
+
+ if (status) {
+ switch (status) {
+ case Status.ALL:
+ return titleCondition;
+
+ case Status.ACTIVE:
+ return titleCondition && !todo.completed;
+
+ case Status.COMPLETED:
+ return titleCondition && todo.completed;
+
+ default:
+ return titleCondition;
+ }
+ }
+
+ return titleCondition;
+ });
+};