diff --git a/README.md b/README.md index 6226417010..d8ffa8aab1 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,30 @@ # React Add TODO Form Implement the ability to add TODOs to the `TodoList` implemented in the **Static List of TODOs** + > Here is [the working example](https://mate-academy.github.io/react_add-todo-form/) 1. Create an `App` component storing the `todos` array and displaying it using the `TodoList`. 1. Create a form to add new TODOs: - - there should be a text input for the `title` with `data-cy="titleInput"` attribute; - - add a `` with `data-cy="userSelect"` attribute showing all the given users; + - add labels and placeholders where they are needed; + - add a new todo to the list after clicking the `Add` button; + - each TODO should have `id`, `title`, `userId`, and `completed` (`false` by default); + - `id` is the largest `id` in the array + 1 (add `data-id={todo.id}` attribute to each `.TodoInfo`). 1. Add validation to the form: - - add a default empty option `Choose a user` to the select; - - before creating a todo, check if a `user` was selected; if not, show an error message next to the `select` (`Please choose a user`); - - if the `title` is empty, show an error message next to the `title` field (`Please enter a title`); - - errors should appear only after clicking the `Add` button; - - hide the message immediately after any change of the field with an error; + - add a default empty option `Choose a user` to the select; + - before creating a todo, check if a `user` was selected; if not, show an error message next to the `select` (`Please choose a user`); + - if the `title` is empty, show an error message next to the `title` field (`Please enter a title`); + - errors should appear only after clicking the `Add` button; + - hide the message immediately after any change of the field with an error; 1. If the form is valid, add a todo to the list and clear the form. -1. (* **Optional**) Allow entering only letters (`ua` and `en`), digits, and `spaces` in the `title` field. Just remove any other characters from the `title`. +1. (\* **Optional**) Allow entering only letters (`ua` and `en`), digits, and `spaces` in the `title` field. Just remove any other characters from the `title`. ## Instructions + - Install Prettier Extention and use this [VSCode settings](https://mate-academy.github.io/fe-program/tools/vscode/settings.json) to enable format on save. - Implement a solution following the [React task guideline](https://github.com/mate-academy/react_task-guideline#react-tasks-guideline). - Use the [React TypeScript cheat sheet](https://mate-academy.github.io/fe-program/js/extra/react-typescript). - Open one more terminal and run tests with `npm test` to ensure your solution is correct. -- Replace `` with your Github username in the [DEMO LINK](https://.github.io/react_add-todo-form/) and add it to the PR description. +- Replace `` with your Github username in the [DEMO LINK](https://clavigo.github.io/react_add-todo-form/) and add it to the PR description. diff --git a/src/App.tsx b/src/App.tsx index a9a9bb4c53..33b7bc8e77 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,61 +1,23 @@ +import React from 'react'; import './App.scss'; -// import usersFromServer from './api/users'; -// import todosFromServer from './api/todos'; +import usersFromServer from './api/users'; +import todosFromServer from './api/todos'; +import { Todo } from './types/Todo'; +import { TodoList } from './components/TodoList'; +import { Form } from './components/Form'; export const App = () => { + const [todos, setTodos] = React.useState(todosFromServer); + const [users] = React.useState(usersFromServer); + return (

Add todo form

-
-
- - Please enter a title -
- -
- - - Please choose a user -
- - -
- -
- - - - - -
+
); }; diff --git a/src/components/Form/Form.tsx b/src/components/Form/Form.tsx new file mode 100644 index 0000000000..85342a0c4f --- /dev/null +++ b/src/components/Form/Form.tsx @@ -0,0 +1,104 @@ +import React from 'react'; +import { Todo } from '../../types/Todo'; +import { User } from '../../types/User'; + +type FormProps = { + todos: Todo[]; + setTodos: React.Dispatch>; + users: User[]; +}; + +export const Form: React.FC = ({ todos, setTodos, users }) => { + const [title, setTitle] = React.useState(''); + const [titleError, setTitleError] = React.useState(false); + + const [select, setSelect] = React.useState(0); + const [selectError, setSelectError] = React.useState(false); + + const handleTitleChange = (e: React.ChangeEvent) => { + setTitle(e.target.value); + setTitleError(false); + }; + + const handleSelectChange = (e: React.ChangeEvent) => { + setSelect(+e.target.value); + setSelectError(false); + }; + + const currentTodos = (newTodo: Todo) => { + setTodos([...todos, newTodo]); + }; + + const getNewId = (todosArray: Todo[]) => { + const maxId = Math.max(...todosArray.map(todo => todo.id)); + + return maxId + 1; + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + setTitleError(!title); + setSelectError(!select); + + if (!title || !select) { + return; + } + + currentTodos({ + id: getNewId(todos), + title, + completed: false, + userId: select, + }); + + setTitle(''); + setSelect(0); + }; + + return ( + +
+ + + + {titleError ? 'Please enter a title' : ''} + +
+ +
+ + + + + {selectError ? 'Please choose a user' : ''} + +
+ + + + ); +}; diff --git a/src/components/Form/index.ts b/src/components/Form/index.ts new file mode 100644 index 0000000000..b690c60a16 --- /dev/null +++ b/src/components/Form/index.ts @@ -0,0 +1 @@ +export * from './Form'; diff --git a/src/components/TodoInfo/TodoInfo.tsx b/src/components/TodoInfo/TodoInfo.tsx index d164511fa8..bbad90bce4 100644 --- a/src/components/TodoInfo/TodoInfo.tsx +++ b/src/components/TodoInfo/TodoInfo.tsx @@ -1 +1,25 @@ -export const TodoInfo = () => {}; +import React from 'react'; +import cn from 'classnames'; +import { UserInfo } from '../UserInfo'; +import { User } from '../../types/User'; + +type TodoInfoProps = { + id: number; + title: string; + completed: boolean; + userId: number; + user: User; +}; + +export const TodoInfo: React.FC = todo => { + return ( +
+

{todo.title}

+ + +
+ ); +}; diff --git a/src/components/TodoList/TodoList.tsx b/src/components/TodoList/TodoList.tsx index c12fae07c0..ec893182db 100644 --- a/src/components/TodoList/TodoList.tsx +++ b/src/components/TodoList/TodoList.tsx @@ -1 +1,31 @@ -export const TodoList = () => {}; +import React from 'react'; +import { TodoInfo } from '../TodoInfo'; +import { Todos } from '../../types/Todos'; +import { User } from '../../types/User'; + +export const TodoList: React.FC = ({ todos, users }) => { + return ( +
+ {todos.map(todo => { + const user = users.find( + (findedUser: User) => findedUser.id === todo.userId, + ); + + if (!user) { + return; + } + + return ( + + ); + })} +
+ ); +}; diff --git a/src/components/UserInfo/UserInfo.tsx b/src/components/UserInfo/UserInfo.tsx index f7bf0410ec..5824b7b711 100644 --- a/src/components/UserInfo/UserInfo.tsx +++ b/src/components/UserInfo/UserInfo.tsx @@ -1 +1,18 @@ -export const UserInfo = () => {}; +import React from 'react'; +import { User } from '../../types/User'; + +type UserInfoProps = { + user: User; +}; + +export const UserInfo: React.FC = ({ user }) => { + return ( + <> + {user && ( + + {user.name} + + )} + + ); +}; diff --git a/src/types/Todo.ts b/src/types/Todo.ts new file mode 100644 index 0000000000..d3bbf2211d --- /dev/null +++ b/src/types/Todo.ts @@ -0,0 +1,6 @@ +export type Todo = { + id: number; + title: string; + completed: boolean; + userId: number; +}; diff --git a/src/types/Todos.ts b/src/types/Todos.ts new file mode 100644 index 0000000000..831d8e0d30 --- /dev/null +++ b/src/types/Todos.ts @@ -0,0 +1,7 @@ +import { Todo } from './Todo'; +import { User } from './User'; + +export type Todos = { + todos: Todo[]; + users: User[]; +}; diff --git a/src/types/User.ts b/src/types/User.ts new file mode 100644 index 0000000000..f74409b994 --- /dev/null +++ b/src/types/User.ts @@ -0,0 +1,6 @@ +export type User = { + id: number; + name: string; + username: string; + email: string; +};