From 3d6b961951b7aab7e523996b5dfcf3274d4b805b Mon Sep 17 00:00:00 2001 From: Oksana Drozdyk Date: Mon, 23 Oct 2023 21:36:40 +0300 Subject: [PATCH] add todo form solution --- src/App.scss | 32 --------- src/App.tsx | 73 +++++++------------- src/components/Form/Form.scss | 8 +++ src/components/Form/Form.tsx | 98 +++++++++++++++++++++++++++ src/components/Form/index.ts | 1 + src/components/TodoInfo/TodoInfo.scss | 18 +++++ src/components/TodoInfo/TodoInfo.tsx | 33 ++++++++- src/components/TodoList/TodoList.tsx | 14 +++- src/components/UserInfo/UserInfo.scss | 3 + src/components/UserInfo/UserInfo.tsx | 21 +++++- src/services.ts | 11 +++ src/types/Todo.ts | 6 ++ src/types/User.ts | 6 ++ 13 files changed, 239 insertions(+), 85 deletions(-) create mode 100644 src/components/Form/Form.scss create mode 100644 src/components/Form/Form.tsx create mode 100644 src/components/Form/index.ts create mode 100644 src/components/TodoInfo/TodoInfo.scss create mode 100644 src/components/UserInfo/UserInfo.scss create mode 100644 src/services.ts create mode 100644 src/types/Todo.ts create mode 100644 src/types/User.ts diff --git a/src/App.scss b/src/App.scss index 9b9ddc979c..9eb101da38 100644 --- a/src/App.scss +++ b/src/App.scss @@ -5,35 +5,3 @@ iframe { html { font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; } - -label, -button { - cursor: pointer; -} - -.error { - color: red; -} - -.TodoInfo { - width: max-content; - padding: 8px; - margin: 12px 0; - border: 1px solid #000; - border-radius: 8px; - background-color: antiquewhite; - - &__title { - margin: 4px 0; - font-size: inherit; - color: #f00; - } - - &--completed &__title { - color: #080; - } -} - -.UserInfo { - font-style: italic; -} diff --git a/src/App.tsx b/src/App.tsx index 9646bf5c6f..4d1236146b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,61 +1,34 @@ import './App.scss'; -// import usersFromServer from './api/users'; -// import todosFromServer from './api/todos'; +import { useState } from 'react'; +import { Form } from './components/Form'; +import { TodoList } from './components/TodoList'; +import { Todo } from './types/Todo'; +import { getNewTodoId } from './services'; + +import todosFromServer from './api/todos'; export const App = () => { + const [todoList, setTodoList] = useState(todosFromServer); + + const addTodo = (todo: Todo) => { + const newTodo = { + ...todo, + id: getNewTodoId(todoList), + }; + + setTodoList(currentList => [...currentList, newTodo]); + }; + return (

Add todo form

-
-
- - Please enter a title -
- -
- - - Please choose a user -
- - -
- -
- - - - - -
+
+ +
); }; diff --git a/src/components/Form/Form.scss b/src/components/Form/Form.scss new file mode 100644 index 0000000000..18595407d0 --- /dev/null +++ b/src/components/Form/Form.scss @@ -0,0 +1,8 @@ +label, +button { + cursor: pointer; +} + +.error { + color: red; +} diff --git a/src/components/Form/Form.tsx b/src/components/Form/Form.tsx new file mode 100644 index 0000000000..cbf67d50c9 --- /dev/null +++ b/src/components/Form/Form.tsx @@ -0,0 +1,98 @@ +import React, { useState } from 'react'; +import usersFromServer from '../../api/users'; +import { Todo } from '../../types/Todo'; + +type Props = { + onAdd: (todo: Todo) => void; +}; + +export const Form: React.FC = ({ onAdd }) => { + const [count, setCount] = useState(0); + const [title, setTitle] = useState(''); + const [hasTitleError, setHasTitleError] = useState(false); + const [selectedUser, setSelectedUser] = useState(0); + const [hasSelectError, setHasSelectError] = useState(false); + + function handleTitleChange(event: React.ChangeEvent) { + setTitle(event.target.value); + setHasTitleError(false); + } + + function handleUserChange(event: React.ChangeEvent) { + setSelectedUser(+event.target.value); + setHasSelectError(false); + } + + function handleSubmit(event: React.FormEvent) { + event.preventDefault(); + + setHasTitleError(!title); + setHasSelectError(!selectedUser); + + if (title && selectedUser) { + onAdd({ + id: 0, + title, + completed: false, + userId: selectedUser, + }); + setCount(count + 1); + setTitle(''); + setSelectedUser(0); + } + } + + return ( + +
+ + + + + {hasTitleError && ( + Please enter a title + )} +
+ +
+ + + + + {hasSelectError && ( + 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.scss b/src/components/TodoInfo/TodoInfo.scss new file mode 100644 index 0000000000..697856a4de --- /dev/null +++ b/src/components/TodoInfo/TodoInfo.scss @@ -0,0 +1,18 @@ +.TodoInfo { + width: max-content; + padding: 8px; + margin: 12px 0; + border: 1px solid #000; + border-radius: 8px; + background-color: antiquewhite; + + &__title { + margin: 4px 0; + font-size: inherit; + color: #f00; + } + + &--completed &__title { + color: #080; + } +} diff --git a/src/components/TodoInfo/TodoInfo.tsx b/src/components/TodoInfo/TodoInfo.tsx index d164511fa8..3cdc805cea 100644 --- a/src/components/TodoInfo/TodoInfo.tsx +++ b/src/components/TodoInfo/TodoInfo.tsx @@ -1 +1,32 @@ -export const TodoInfo = () => {}; +import './TodoInfo.scss'; + +import React from 'react'; +import { UserInfo } from '../UserInfo'; +import { Todo } from '../../types/Todo'; +import { getUserById } from '../../services'; + +type Props = { + todo: Todo; +}; + +export const TodoInfo:React.FC = ({ todo }) => { + const { + title, + completed, + userId, + } = todo; + + const user = getUserById(userId); + + return ( +
+

{title}

+ + {user && } + +
+ ); +}; diff --git a/src/components/TodoList/TodoList.tsx b/src/components/TodoList/TodoList.tsx index c12fae07c0..2f49caf827 100644 --- a/src/components/TodoList/TodoList.tsx +++ b/src/components/TodoList/TodoList.tsx @@ -1 +1,13 @@ -export const TodoList = () => {}; +import React from 'react'; +import { Todo } from '../../types/Todo'; +import { TodoInfo } from '../TodoInfo'; + +type Props = { + todos: Todo[]; +}; + +export const TodoList:React.FC = ({ todos }) => ( +
+ {todos.map(todo => )} +
+); diff --git a/src/components/UserInfo/UserInfo.scss b/src/components/UserInfo/UserInfo.scss new file mode 100644 index 0000000000..b8f4e81a47 --- /dev/null +++ b/src/components/UserInfo/UserInfo.scss @@ -0,0 +1,3 @@ +.UserInfo { + font-style: italic; +} diff --git a/src/components/UserInfo/UserInfo.tsx b/src/components/UserInfo/UserInfo.tsx index f7bf0410ec..16b4788547 100644 --- a/src/components/UserInfo/UserInfo.tsx +++ b/src/components/UserInfo/UserInfo.tsx @@ -1 +1,20 @@ -export const UserInfo = () => {}; +import React from 'react'; +import { User } from '../../types/User'; +import './UserInfo.scss'; + +type Props = { + user: User; +}; + +export const UserInfo:React.FC = ({ user }) => { + const { + name, + email, + } = user; + + return ( + + {name} + + ); +}; diff --git a/src/services.ts b/src/services.ts new file mode 100644 index 0000000000..855230998e --- /dev/null +++ b/src/services.ts @@ -0,0 +1,11 @@ +import { User } from './types/User'; +import { Todo } from './types/Todo'; +import usersFromServer from './api/users'; + +export function getUserById(userId: number): User | null { + return usersFromServer.find(user => user.id === userId) || null; +} + +export function getNewTodoId(todos: Todo[]): number { + return Math.max(...todos.map(todo => todo.id)) + 1; +} diff --git a/src/types/Todo.ts b/src/types/Todo.ts new file mode 100644 index 0000000000..780afae02d --- /dev/null +++ b/src/types/Todo.ts @@ -0,0 +1,6 @@ +export interface Todo { + id: number; + title: string; + completed: boolean; + userId: number; +} diff --git a/src/types/User.ts b/src/types/User.ts new file mode 100644 index 0000000000..1f6908b55a --- /dev/null +++ b/src/types/User.ts @@ -0,0 +1,6 @@ +export interface User { + id: number; + name: string; + username: string; + email: string; +}